Compare commits

...
23 Commits
Author SHA1 Message Date
Cedric Verstraeten aac2150a3a [release] v3.1.1 2024-01-07 22:14:44 +01:00
Cedric Verstraeten 9b713637b9 change version number of ui 2024-01-07 21:44:32 +01:00
Cedric Verstraeten 699660d472 only make release when putting [release] 2024-01-07 21:41:32 +01:00
Cedric Verstraeten 751aa17534 feature: make hub encryption configurable + only send heartbeat to vault when credentials are set 2024-01-07 21:30:57 +01:00
Cedric Verstraeten 2681bd2fe3 hot fix: keep track of main and sub stream separately (one of them might block) 2024-01-07 20:20:51 +01:00
Cedric Verstraeten 93adb3dabc different order in action 2024-01-07 08:29:53 +01:00
Cedric Verstraeten 0e15e58a88 try once more different format 2024-01-07 08:26:34 +01:00
Cedric Verstraeten ef2ea999df only run release to docker when containing [release] 2024-01-07 08:22:24 +01:00
Cedric Verstraeten ca367611d7 Update docker-nightly.yml 2024-01-07 08:15:24 +01:00
Cedric Verstraeten eb8f073856 Merge branch 'master' into develop 2024-01-03 22:03:00 +01:00
Cedric Verstraeten 3ae43eba16 hot fix: close client on verifying connection (will keep client open) 2024-01-03 22:02:44 +01:00
Cedric Verstraeten 9719a08eaa Merge branch 'master' into develop 2024-01-03 21:54:30 +01:00
Cedric Verstraeten 1e165cbeb8 hotfix: try to create pullpoint subscription if first time failed 2024-01-03 18:44:53 +01:00
Cedric Verstraeten 8be8cafd00 force release mode in GIN 2024-01-03 18:26:10 +01:00
Cedric Verstraeten e74d2aadb5 Merge branch 'develop' 2024-01-03 18:16:23 +01:00
Cedric Verstraeten 9c97422f43 properly handle cameras without PTZ function 2024-01-03 18:12:02 +01:00
Cedric Verstraeten deb0a3ff1f hotfix: position or zoom can be nil 2024-01-03 13:37:38 +01:00
Cedric Verstraeten 95ed1f0e97 move error to debug 2024-01-03 12:36:08 +01:00
Cedric Verstraeten 6a111dadd6 typo in readme (wrong formatting link) 2024-01-03 12:24:35 +01:00
Cedric Verstraeten 95b3623c04 change startup command (new flag method) 2024-01-03 12:19:18 +01:00
Cedric Verstraeten 326d62a640 snap was moved to dedicated repository to better control release: https://github.com/kerberos-io/snap-agent
the repository https://github.com/kerberos-io/snap-agent is linked to the snap build system and will generate new releases
2024-01-03 12:17:47 +01:00
Cedric Verstraeten 9d990650f3 hotfix: onvif endpoint changed 2024-01-03 10:19:04 +01:00
Cedric Verstraeten 4bc891b640 hotfix: move from warning to debug 2024-01-03 10:12:18 +01:00
23 changed files with 241 additions and 167 deletions
+4
View File
@@ -6,6 +6,8 @@ on:
jobs: jobs:
build-amd64: build-amd64:
# If contains the keyword "#release" in the commit message.
if: ${{ !contains(github.event.head_commit.message, '#release') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -31,6 +33,8 @@ jobs:
- name: Create new and append to latest manifest - name: Create new and append to latest manifest
run: docker buildx imagetools create -t kerberos/agent-dev:latest kerberos/agent-dev:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) run: docker buildx imagetools create -t kerberos/agent-dev:latest kerberos/agent-dev:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
build-other: build-other:
# If contains the keyword "#release" in the commit message.
if: ${{ !contains(github.event.head_commit.message, '#release') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
+8 -4
View File
@@ -7,6 +7,8 @@ on:
jobs: jobs:
build-amd64: build-amd64:
# If contains the keyword "[release]" in the commit message.
if: "contains(github.event.head_commit.message, '[release]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -26,10 +28,12 @@ jobs:
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Run Buildx - name: Run Buildx
run: docker buildx build --platform linux/${{matrix.architecture}} -t kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) --push . run: cd agent && docker buildx build --platform linux/${{matrix.architecture}} -t kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) --push .
- name: Create new and append to manifest - name: Create new and append to manifest
run: docker buildx imagetools create -t kerberos/agent-nightly:$(echo $GITHUB_SHA | cut -c1-7) kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) run: cd agent && docker buildx imagetools create -t kerberos/agent-nightly:$(echo $GITHUB_SHA | cut -c1-7) kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
build-other: build-other:
# If contains the keyword "[release]" in the commit message.
if: "contains(github.event.head_commit.message, '[release]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -49,6 +53,6 @@ jobs:
- name: Available platforms - name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} run: echo ${{ steps.buildx.outputs.platforms }}
- name: Run Buildx - name: Run Buildx
run: docker buildx build --platform linux/${{matrix.architecture}} -t kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) --push . run: cd agent && docker buildx build --platform linux/${{matrix.architecture}} -t kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) --push .
- name: Create new and append to manifest - name: Create new and append to manifest
run: docker buildx imagetools create --append -t kerberos/agent-nightly:$(echo $GITHUB_SHA | cut -c1-7) kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7) run: cd agent && docker buildx imagetools create --append -t kerberos/agent-nightly:$(echo $GITHUB_SHA | cut -c1-7) kerberos/agent-nightly:arch-$(echo ${{matrix.architecture}} | tr / -)-$(echo $GITHUB_SHA | cut -c1-7)
+5
View File
@@ -2,6 +2,7 @@ name: Docker master build
on: on:
push: push:
# If pushed to master branch.
branches: [ master ] branches: [ master ]
env: env:
@@ -9,6 +10,8 @@ env:
jobs: jobs:
build-amd64: build-amd64:
# If contains the keyword "[release]" in the commit message.
if: "contains(github.event.head_commit.message, '[release]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@@ -67,6 +70,8 @@ jobs:
#- name: Use Snapcraft #- name: Use Snapcraft
# run: tar -xf agent-${{matrix.architecture}}.tar && snapcraft # run: tar -xf agent-${{matrix.architecture}}.tar && snapcraft
build-other: build-other:
# If contains the keyword "[release]" in the commit message.
if: "contains(github.event.head_commit.message, '[release]')"
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
+2 -1
View File
@@ -111,7 +111,7 @@ This repository contains everything you'll need to know about our core product,
- Simplified and modern user interface. - Simplified and modern user interface.
- Multi architecture (ARMv7, ARMv8, amd64, etc).). - Multi architecture (ARMv7, ARMv8, amd64, etc).).
- Multi stream, for example recording in H265, live streaming and motion detection in H264. - Multi stream, for example recording in H265, live streaming and motion detection in H264.
- Multi camera support: IP Cameras (H264 and H265), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp - Multi camera support: IP Cameras (H264 and H265), USB cameras and Raspberry Pi Cameras [through a RTSP proxy](https://github.com/kerberos-io/camera-to-rtsp).
- Single camera per instance (e.g. one container per camera). - Single camera per instance (e.g. one container per camera).
- Low resolution streaming through MQTT and high resolution streaming through WebRTC (only supports H264/PCM). - Low resolution streaming through MQTT and high resolution streaming through WebRTC (only supports H264/PCM).
- Backchannel audio from Kerberos Hub to IP camera (requires PCM ULAW codec) - Backchannel audio from Kerberos Hub to IP camera (requires PCM ULAW codec)
@@ -226,6 +226,7 @@ Next to attaching the configuration file, it is also possible to override the co
| `AGENT_TURN_USERNAME` | TURN username used for WebRTC. | "username1" | | `AGENT_TURN_USERNAME` | TURN username used for WebRTC. | "username1" |
| `AGENT_TURN_PASSWORD` | TURN password used for WebRTC. | "password1" | | `AGENT_TURN_PASSWORD` | TURN password used for WebRTC. | "password1" |
| `AGENT_CLOUD` | Store recordings in Kerberos Hub (s3), Kerberos Vault (kstorage) or Dropbox (dropbox). | "s3" | | `AGENT_CLOUD` | Store recordings in Kerberos Hub (s3), Kerberos Vault (kstorage) or Dropbox (dropbox). | "s3" |
| `AGENT_HUB_ENCRYPTION` | Turning on/off encryption of traffic from your Kerberos Agent to Kerberos Hub. | "true" |
| `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.hub.domain.com" | | `AGENT_HUB_URI` | The Kerberos Hub API, defaults to our Kerberos Hub SAAS. | "https://api.hub.domain.com" |
| `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" | | `AGENT_HUB_KEY` | The access key linked to your account in Kerberos Hub. | "" |
| `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" | | `AGENT_HUB_PRIVATE_KEY` | The secret access key linked to your account in Kerberos Hub. | "" |
+2 -2
View File
@@ -9,7 +9,7 @@ Kerberos Agents are now also shipped as static binaries. Within the Docker image
You can run the binary as following on port `8080`: You can run the binary as following on port `8080`:
main run cameraname 8080 main -action=run -port=80
## Systemd ## Systemd
@@ -18,7 +18,7 @@ When running on a Linux OS you might consider to auto-start the Kerberos Agent u
[Unit] [Unit]
Wants=network.target Wants=network.target
[Service] [Service]
ExecStart=/home/pi/agent/main run camera 80 ExecStart=/home/pi/agent/main -action=run -port=80
WorkingDirectory=/home/pi/agent/ WorkingDirectory=/home/pi/agent/
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
+1
View File
@@ -107,6 +107,7 @@
"turn_username": "username1", "turn_username": "username1",
"turn_password": "password1", "turn_password": "password1",
"heartbeaturi": "", "heartbeaturi": "",
"hub_encryption": "true",
"hub_uri": "https://api.cloud.kerberos.io", "hub_uri": "https://api.cloud.kerberos.io",
"hub_key": "", "hub_key": "",
"hub_private_key": "", "hub_private_key": "",
+34 -34
View File
@@ -279,6 +279,40 @@ const docTemplate = `{
} }
} }
}, },
"/api/camera/onvif/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the ONVIF connectivity.",
"tags": [
"onvif"
],
"summary": "Will verify the ONVIF connectivity.",
"operationId": "verify-onvif",
"parameters": [
{
"description": "OnvifCredentials",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.OnvifCredentials"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/camera/onvif/zoom": { "/api/camera/onvif/zoom": {
"post": { "post": {
"description": "Zooming in or out the camera.", "description": "Zooming in or out the camera.",
@@ -590,40 +624,6 @@ const docTemplate = `{
} }
} }
}, },
"/api/onvif/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the ONVIF connectivity.",
"tags": [
"onvif"
],
"summary": "Will verify the ONVIF connectivity.",
"operationId": "verify-onvif",
"parameters": [
{
"description": "OnvifCredentials",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.OnvifCredentials"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/persistence/verify": { "/api/persistence/verify": {
"post": { "post": {
"security": [ "security": [
+34 -34
View File
@@ -271,6 +271,40 @@
} }
} }
}, },
"/api/camera/onvif/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the ONVIF connectivity.",
"tags": [
"onvif"
],
"summary": "Will verify the ONVIF connectivity.",
"operationId": "verify-onvif",
"parameters": [
{
"description": "OnvifCredentials",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.OnvifCredentials"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/camera/onvif/zoom": { "/api/camera/onvif/zoom": {
"post": { "post": {
"description": "Zooming in or out the camera.", "description": "Zooming in or out the camera.",
@@ -582,40 +616,6 @@
} }
} }
}, },
"/api/onvif/verify": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "Will verify the ONVIF connectivity.",
"tags": [
"onvif"
],
"summary": "Will verify the ONVIF connectivity.",
"operationId": "verify-onvif",
"parameters": [
{
"description": "OnvifCredentials",
"name": "config",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.OnvifCredentials"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.APIResponse"
}
}
}
}
},
"/api/persistence/verify": { "/api/persistence/verify": {
"post": { "post": {
"security": [ "security": [
+21 -21
View File
@@ -494,6 +494,27 @@ paths:
summary: Will return the ONVIF presets for the specific camera. summary: Will return the ONVIF presets for the specific camera.
tags: tags:
- onvif - onvif
/api/camera/onvif/verify:
post:
description: Will verify the ONVIF connectivity.
operationId: verify-onvif
parameters:
- description: OnvifCredentials
in: body
name: config
required: true
schema:
$ref: '#/definitions/models.OnvifCredentials'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.APIResponse'
security:
- Bearer: []
summary: Will verify the ONVIF connectivity.
tags:
- onvif
/api/camera/onvif/zoom: /api/camera/onvif/zoom:
post: post:
description: Zooming in or out the camera. description: Zooming in or out the camera.
@@ -700,27 +721,6 @@ paths:
summary: Get Authorization token. summary: Get Authorization token.
tags: tags:
- authentication - authentication
/api/onvif/verify:
post:
description: Will verify the ONVIF connectivity.
operationId: verify-onvif
parameters:
- description: OnvifCredentials
in: body
name: config
required: true
schema:
$ref: '#/definitions/models.OnvifCredentials'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.APIResponse'
security:
- Bearer: []
summary: Will verify the ONVIF connectivity.
tags:
- onvif
/api/persistence/verify: /api/persistence/verify:
post: post:
description: Will verify the persistence. description: Will verify the persistence.
+18 -4
View File
@@ -351,7 +351,7 @@ func (g *Golibrtsp) ConnectBackChannel(ctx context.Context) (err error) {
} }
// Start the RTSP client, and start reading packets. // Start the RTSP client, and start reading packets.
func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) (err error) { func (g *Golibrtsp) Start(ctx context.Context, streamType string, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) (err error) {
log.Log.Debug("capture.golibrtsp.Start(): started") log.Log.Debug("capture.golibrtsp.Start(): started")
// called when a MULAW audio RTP packet arrives // called when a MULAW audio RTP packet arrives
@@ -360,7 +360,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati
// decode timestamp // decode timestamp
pts, ok := g.Client.PacketPTS(g.AudioG711Media, rtppkt) pts, ok := g.Client.PacketPTS(g.AudioG711Media, rtppkt)
if !ok { if !ok {
log.Log.Warning("capture.golibrtsp.Start(): " + "unable to get PTS") log.Log.Debug("capture.golibrtsp.Start(): " + "unable to get PTS")
return return
} }
@@ -443,7 +443,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati
// decode timestamp // decode timestamp
pts, ok := g.Client.PacketPTS(g.VideoH264Media, rtppkt) pts, ok := g.Client.PacketPTS(g.VideoH264Media, rtppkt)
if !ok { if !ok {
log.Log.Warning("capture.golibrtsp.Start(): " + "unable to get PTS") log.Log.Debug("capture.golibrtsp.Start(): " + "unable to get PTS")
return return
} }
@@ -527,10 +527,17 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati
if idrPresent { if idrPresent {
// Increment packets, so we know the device // Increment packets, so we know the device
// is not blocking. // is not blocking.
if streamType == "main" {
r := communication.PackageCounter.Load().(int64) r := communication.PackageCounter.Load().(int64)
log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data))) log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data)))
communication.PackageCounter.Store((r + 1) % 1000) communication.PackageCounter.Store((r + 1) % 1000)
communication.LastPacketTimer.Store(time.Now().Unix()) communication.LastPacketTimer.Store(time.Now().Unix())
} else if streamType == "sub" {
r := communication.PackageCounterSub.Load().(int64)
log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data)))
communication.PackageCounterSub.Store((r + 1) % 1000)
communication.LastPacketTimerSub.Store(time.Now().Unix())
}
} }
} }
@@ -554,7 +561,7 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati
// decode timestamp // decode timestamp
pts, ok := g.Client.PacketPTS(g.VideoH265Media, rtppkt) pts, ok := g.Client.PacketPTS(g.VideoH265Media, rtppkt)
if !ok { if !ok {
log.Log.Warning("capture.golibrtsp.Start(): " + "unable to get PTS") log.Log.Debug("capture.golibrtsp.Start(): " + "unable to get PTS")
return return
} }
@@ -637,10 +644,17 @@ func (g *Golibrtsp) Start(ctx context.Context, queue *packets.Queue, configurati
if isRandomAccess { if isRandomAccess {
// Increment packets, so we know the device // Increment packets, so we know the device
// is not blocking. // is not blocking.
if streamType == "main" {
r := communication.PackageCounter.Load().(int64) r := communication.PackageCounter.Load().(int64)
log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data))) log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data)))
communication.PackageCounter.Store((r + 1) % 1000) communication.PackageCounter.Store((r + 1) % 1000)
communication.LastPacketTimer.Store(time.Now().Unix()) communication.LastPacketTimer.Store(time.Now().Unix())
} else if streamType == "sub" {
r := communication.PackageCounterSub.Load().(int64)
log.Log.Debug("capture.golibrtsp.Start(): packet size " + strconv.Itoa(len(pkt.Data)))
communication.PackageCounterSub.Store((r + 1) % 1000)
communication.LastPacketTimerSub.Store(time.Now().Unix())
}
} }
} }
+1 -1
View File
@@ -44,7 +44,7 @@ type RTSPClient interface {
ConnectBackChannel(ctx context.Context) error ConnectBackChannel(ctx context.Context) error
// Start the RTSP client, and start reading packets. // Start the RTSP client, and start reading packets.
Start(ctx context.Context, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) error Start(ctx context.Context, streamType string, queue *packets.Queue, configuration *models.Configuration, communication *models.Communication) error
// Start the RTSP client, and start reading packets. // Start the RTSP client, and start reading packets.
StartBackChannel(ctx context.Context) (err error) StartBackChannel(ctx context.Context) (err error)
+7 -1
View File
@@ -564,6 +564,8 @@ func VerifyCamera(c *gin.Context) {
} }
} }
err := rtspClient.Close()
if err == nil {
if videoIdx > -1 { if videoIdx > -1 {
c.JSON(200, models.APIResponse{ c.JSON(200, models.APIResponse{
Message: "All good, detected a H264 codec.", Message: "All good, detected a H264 codec.",
@@ -574,7 +576,11 @@ func VerifyCamera(c *gin.Context) {
Message: "Stream doesn't have a H264 codec, we only support H264 so far.", Message: "Stream doesn't have a H264 codec, we only support H264 so far.",
}) })
} }
} else {
c.JSON(400, models.APIResponse{
Message: "Something went wrong while closing the connection " + err.Error(),
})
}
} else { } else {
c.JSON(400, models.APIResponse{ c.JSON(400, models.APIResponse{
Message: err.Error(), Message: err.Error(),
+12 -3
View File
@@ -290,7 +290,7 @@ loop:
onvifPresetsList = []byte("[]") onvifPresetsList = []byte("[]")
} }
} else { } else {
log.Log.Error("cloud.HandleHeartBeat(): error while getting PTZ configurations: " + err.Error()) log.Log.Debug("cloud.HandleHeartBeat(): error while getting PTZ configurations: " + err.Error())
onvifPresetsList = []byte("[]") onvifPresetsList = []byte("[]")
} }
@@ -320,6 +320,12 @@ loop:
} else { } else {
log.Log.Debug("cloud.HandleHeartBeat(): no pull point address found.") log.Log.Debug("cloud.HandleHeartBeat(): no pull point address found.")
onvifEventsList = []byte("[]") onvifEventsList = []byte("[]")
// Try again
pullPointAddress, err = onvif.CreatePullPointSubscription(device)
if err != nil {
log.Log.Debug("cloud.HandleHeartBeat(): error while creating pull point subscription: " + err.Error())
}
} }
} else { } else {
@@ -444,8 +450,9 @@ loop:
}`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, onvifEventsList, cameraConnected, hasBackChannel) }`, config.Key, system.Version, system.CPUId, username, key, name, isEnterprise, system.Hostname, system.Architecture, system.TotalMemory, system.UsedMemory, system.FreeMemory, system.ProcessUsedMemory, macs, ips, "0", "0", "0", uptimeString, boottimeString, config.HubSite, onvifEnabled, onvifZoom, onvifPanTilt, onvifPresets, onvifPresetsList, onvifEventsList, cameraConnected, hasBackChannel)
// Get the private key to encrypt the data using symmetric encryption: AES. // Get the private key to encrypt the data using symmetric encryption: AES.
HubEncryption := config.HubEncryption
privateKey := config.HubPrivateKey privateKey := config.HubPrivateKey
if privateKey != "" { if HubEncryption == "true" && privateKey != "" {
// Encrypt the data using AES. // Encrypt the data using AES.
encrypted, err := encryption.AesEncrypt([]byte(object), privateKey) encrypted, err := encryption.AesEncrypt([]byte(object), privateKey)
if err != nil { if err != nil {
@@ -486,7 +493,9 @@ loop:
// If we have a Kerberos Vault connected, we will also send some analytics // If we have a Kerberos Vault connected, we will also send some analytics
// to that service. // to that service.
vaultURI = config.KStorage.URI vaultURI = config.KStorage.URI
if vaultURI != "" { accessKey := config.KStorage.AccessKey
secretAccessKey := config.KStorage.SecretAccessKey
if vaultURI != "" && accessKey != "" && secretAccessKey != "" {
var object = fmt.Sprintf(`{ var object = fmt.Sprintf(`{
"key" : "%s", "key" : "%s",
+51 -4
View File
@@ -36,12 +36,20 @@ func Bootstrap(configDirectory string, configuration *models.Configuration, comm
packageCounter.Store(int64(0)) packageCounter.Store(int64(0))
communication.PackageCounter = &packageCounter communication.PackageCounter = &packageCounter
var packageCounterSub atomic.Value
packageCounterSub.Store(int64(0))
communication.PackageCounterSub = &packageCounterSub
// This is used when the last packet was received (timestamp), // This is used when the last packet was received (timestamp),
// this metric is used to determine if the camera is still online/connected. // this metric is used to determine if the camera is still online/connected.
var lastPacketTimer atomic.Value var lastPacketTimer atomic.Value
packageCounter.Store(int64(0)) packageCounter.Store(int64(0))
communication.LastPacketTimer = &lastPacketTimer communication.LastPacketTimer = &lastPacketTimer
var lastPacketTimerSub atomic.Value
packageCounterSub.Store(int64(0))
communication.LastPacketTimerSub = &lastPacketTimerSub
// This is used to understand if we have a working Kerberos Hub connection // This is used to understand if we have a working Kerberos Hub connection
// cloudTimestamp will be updated when successfully sending heartbeats. // cloudTimestamp will be updated when successfully sending heartbeats.
var cloudTimestamp atomic.Value var cloudTimestamp atomic.Value
@@ -245,7 +253,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
log.Log.Info("components.Kerberos.RunAgent(): SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1)) log.Log.Info("components.Kerberos.RunAgent(): SetMaxGopCount was set with: " + strconv.Itoa(int(config.Capture.PreRecording)+1))
queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). queue.SetMaxGopCount(int(config.Capture.PreRecording) + 1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room).
queue.WriteHeader(videoStreams) queue.WriteHeader(videoStreams)
go rtspClient.Start(context.Background(), queue, configuration, communication) go rtspClient.Start(context.Background(), "main", queue, configuration, communication)
// Main stream is connected and ready to go.
communication.MainStreamConnected = true
// Try to create backchannel // Try to create backchannel
rtspBackChannelClient := captureDevice.SetBackChannelClient(rtspUrl) rtspBackChannelClient := captureDevice.SetBackChannelClient(rtspUrl)
@@ -261,7 +272,10 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
communication.SubQueue = subQueue communication.SubQueue = subQueue
subQueue.SetMaxGopCount(1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room). subQueue.SetMaxGopCount(1) // GOP time frame is set to prerecording (we'll add 2 gops to leave some room).
subQueue.WriteHeader(videoSubStreams) subQueue.WriteHeader(videoSubStreams)
go rtspSubClient.Start(context.Background(), subQueue, configuration, communication) go rtspSubClient.Start(context.Background(), "sub", subQueue, configuration, communication)
// Sub stream is connected and ready to go.
communication.SubStreamConnected = true
} }
// Handle livestream SD (low resolution over MQTT) // Handle livestream SD (low resolution over MQTT)
@@ -320,6 +334,8 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
// If we reach this point, we are stopping the stream. // If we reach this point, we are stopping the stream.
communication.CameraConnected = false communication.CameraConnected = false
communication.MainStreamConnected = false
communication.SubStreamConnected = false
// Cancel the main context, this will stop all the other goroutines. // Cancel the main context, this will stop all the other goroutines.
(*communication.CancelContext)() (*communication.CancelContext)()
@@ -397,14 +413,19 @@ func RunAgent(configDirectory string, configuration *models.Configuration, commu
func ControlAgent(communication *models.Communication) { func ControlAgent(communication *models.Communication) {
log.Log.Debug("components.Kerberos.ControlAgent(): started") log.Log.Debug("components.Kerberos.ControlAgent(): started")
packageCounter := communication.PackageCounter packageCounter := communication.PackageCounter
packageSubCounter := communication.PackageCounterSub
go func() { go func() {
// A channel to check the camera activity // A channel to check the camera activity
var previousPacket int64 = 0 var previousPacket int64 = 0
var previousPacketSub int64 = 0
var occurence = 0 var occurence = 0
var occurenceSub = 0
for { for {
// If camera is connected, we'll check if we are still receiving packets. // If camera is connected, we'll check if we are still receiving packets.
if communication.CameraConnected { if communication.CameraConnected {
// First we'll check the main stream.
packetsR := packageCounter.Load().(int64) packetsR := packageCounter.Load().(int64)
if packetsR == previousPacket { if packetsR == previousPacket {
// If we are already reconfiguring, // If we are already reconfiguring,
@@ -416,16 +437,42 @@ func ControlAgent(communication *models.Communication) {
occurence = 0 occurence = 0
} }
log.Log.Info("components.Kerberos.ControlAgent(): Number of packets read " + strconv.FormatInt(packetsR, 10)) log.Log.Info("components.Kerberos.ControlAgent(): Number of packets read from mainstream: " + strconv.FormatInt(packetsR, 10))
// After 15 seconds without activity this is thrown.. // After 15 seconds without activity this is thrown..
if occurence == 3 { if occurence == 3 {
log.Log.Info("components.Kerberos.ControlAgent(): Restarting machinery.") log.Log.Info("components.Kerberos.ControlAgent(): Restarting machinery because of blocking mainstream.")
communication.HandleBootstrap <- "restart" communication.HandleBootstrap <- "restart"
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
occurence = 0 occurence = 0
} }
// Now we'll check the sub stream.
packetsSubR := packageSubCounter.Load().(int64)
if communication.SubStreamConnected {
if packetsSubR == previousPacketSub {
// If we are already reconfiguring,
// we dont need to check if the stream is blocking.
if !communication.IsConfiguring.IsSet() {
occurenceSub = occurenceSub + 1
}
} else {
occurenceSub = 0
}
log.Log.Info("components.Kerberos.ControlAgent(): Number of packets read from substream: " + strconv.FormatInt(packetsSubR, 10))
// After 15 seconds without activity this is thrown..
if occurenceSub == 3 {
log.Log.Info("components.Kerberos.ControlAgent(): Restarting machinery because of blocking substream.")
communication.HandleBootstrap <- "restart"
time.Sleep(2 * time.Second)
occurenceSub = 0
}
}
previousPacket = packageCounter.Load().(int64) previousPacket = packageCounter.Load().(int64)
previousPacketSub = packageSubCounter.Load().(int64)
} }
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
+3
View File
@@ -406,6 +406,9 @@ func OverrideWithEnvironmentVariables(configuration *models.Configuration) {
break break
/* When connected and storing in Kerberos Hub (SAAS) */ /* When connected and storing in Kerberos Hub (SAAS) */
case "AGENT_HUB_ENCRYPTION":
configuration.Config.HubEncryption = value
break
case "AGENT_HUB_URI": case "AGENT_HUB_URI":
configuration.Config.HubURI = value configuration.Config.HubURI = value
break break
+4
View File
@@ -15,6 +15,8 @@ type Communication struct {
CancelContext *context.CancelFunc CancelContext *context.CancelFunc
PackageCounter *atomic.Value PackageCounter *atomic.Value
LastPacketTimer *atomic.Value LastPacketTimer *atomic.Value
PackageCounterSub *atomic.Value
LastPacketTimerSub *atomic.Value
CloudTimestamp *atomic.Value CloudTimestamp *atomic.Value
HandleBootstrap chan string HandleBootstrap chan string
HandleStream chan string HandleStream chan string
@@ -33,5 +35,7 @@ type Communication struct {
SubQueue *packets.Queue SubQueue *packets.Queue
Image string Image string
CameraConnected bool CameraConnected bool
MainStreamConnected bool
SubStreamConnected bool
HasBackChannel bool HasBackChannel bool
} }
+1
View File
@@ -37,6 +37,7 @@ type Config struct {
TURNUsername string `json:"turn_username" bson:"turn_username"` TURNUsername string `json:"turn_username" bson:"turn_username"`
TURNPassword string `json:"turn_password" bson:"turn_password"` TURNPassword string `json:"turn_password" bson:"turn_password"`
HeartbeatURI string `json:"heartbeaturi" bson:"heartbeaturi"` /*obsolete*/ HeartbeatURI string `json:"heartbeaturi" bson:"heartbeaturi"` /*obsolete*/
HubEncryption string `json:"hub_encryption" bson:"hub_encryption"`
HubURI string `json:"hub_uri" bson:"hub_uri"` HubURI string `json:"hub_uri" bson:"hub_uri"`
HubKey string `json:"hub_key" bson:"hub_key"` HubKey string `json:"hub_key" bson:"hub_key"`
HubPrivateKey string `json:"hub_private_key" bson:"hub_private_key"` HubPrivateKey string `json:"hub_private_key" bson:"hub_private_key"`
+7 -2
View File
@@ -312,12 +312,17 @@ func GetPositionFromDevice(configuration models.Configuration) (xsdonvif.PTZVect
// Get the PTZ configurations from the device // Get the PTZ configurations from the device
position, err := GetPosition(device, token) position, err := GetPosition(device, token)
if err == nil { if err == nil {
if position.PanTilt != nil && position.Zoom != nil {
// float to string // float to string
x := strconv.FormatFloat(position.PanTilt.X, 'f', 6, 64) x := strconv.FormatFloat(position.PanTilt.X, 'f', 6, 64)
y := strconv.FormatFloat(position.PanTilt.Y, 'f', 6, 64) y := strconv.FormatFloat(position.PanTilt.Y, 'f', 6, 64)
z := strconv.FormatFloat(position.Zoom.X, 'f', 6, 64) z := strconv.FormatFloat(position.Zoom.X, 'f', 6, 64)
log.Log.Info("onvif.GetPositionFromDevice(): successfully got position (" + x + ", " + y + ", " + z + ")") log.Log.Info("onvif.GetPositionFromDevice(): successfully got position (" + x + ", " + y + ", " + z + ")")
return position, err return position, err
} else {
log.Log.Debug("onvif.GetPositionFromDevice(): position is nil")
return position, errors.New("position is nil")
}
} else { } else {
log.Log.Debug("onvif.GetPositionFromDevice(): " + err.Error()) log.Log.Debug("onvif.GetPositionFromDevice(): " + err.Error())
return position, err return position, err
@@ -893,7 +898,7 @@ func GetPTZFunctionsFromDevice(configurations ptz.GetConfigurationsResponse) ([]
} }
// VerifyOnvifConnection godoc // VerifyOnvifConnection godoc
// @Router /api/onvif/verify [post] // @Router /api/camera/onvif/verify [post]
// @ID verify-onvif // @ID verify-onvif
// @Security Bearer // @Security Bearer
// @securityDefinitions.apikey Bearer // @securityDefinitions.apikey Bearer
@@ -987,7 +992,7 @@ func CreatePullPointSubscription(dev *onvif.Device) (string, error) {
stringBody := string(b2) stringBody := string(b2)
decodedXML, et, err := getXMLNode(stringBody, "CreatePullPointSubscriptionResponse") decodedXML, et, err := getXMLNode(stringBody, "CreatePullPointSubscriptionResponse")
if err != nil { if err != nil {
log.Log.Error("onvif.main.CreatePullPointSubscription(): " + err.Error()) log.Log.Debug("onvif.main.CreatePullPointSubscription(): " + err.Error())
} else { } else {
if err := decodedXML.DecodeElement(&createPullPointSubscriptionResponse, et); err != nil { if err := decodedXML.DecodeElement(&createPullPointSubscriptionResponse, et); err != nil {
log.Log.Error("onvif.main.CreatePullPointSubscription(): " + err.Error()) log.Log.Error("onvif.main.CreatePullPointSubscription(): " + err.Error())
+3
View File
@@ -41,6 +41,9 @@ import (
func StartServer(configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) { func StartServer(configDirectory string, configuration *models.Configuration, communication *models.Communication, captureDevice *capture.Capture) {
// Set release mode
gin.SetMode(gin.ReleaseMode)
// Initialize REST API // Initialize REST API
r := gin.Default() r := gin.Default()
+2 -6
View File
@@ -54,11 +54,6 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect
components.UpdateConfig(c, configDirectory, configuration, communication) components.UpdateConfig(c, configDirectory, configuration, communication)
}) })
// Will verify the current onvif settings.
api.POST("/onvif/verify", func(c *gin.Context) {
onvif.VerifyOnvifConnection(c)
})
// Will verify the current hub settings. // Will verify the current hub settings.
api.POST("/hub/verify", func(c *gin.Context) { api.POST("/hub/verify", func(c *gin.Context) {
cloud.VerifyHub(c) cloud.VerifyHub(c)
@@ -94,7 +89,8 @@ func AddRoutes(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware, configDirect
}) })
// Onvif specific methods. Doesn't require any authorization. // Onvif specific methods. Doesn't require any authorization.
// Will verify the current onvif settings.
api.POST("/camera/onvif/verify", onvif.VerifyOnvifConnection)
api.POST("/camera/onvif/login", LoginToOnvif) api.POST("/camera/onvif/login", LoginToOnvif)
api.POST("/camera/onvif/capabilities", GetOnvifCapabilities) api.POST("/camera/onvif/capabilities", GetOnvifCapabilities)
api.POST("/camera/onvif/presets", GetOnvifPresets) api.POST("/camera/onvif/presets", GetOnvifPresets)
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh -e
cp -R $SNAP/data $SNAP_COMMON/
cp -R $SNAP/www $SNAP_COMMON/
cp -R $SNAP/version $SNAP_COMMON/
cp -R $SNAP/mp4fragment $SNAP_COMMON/
-23
View File
@@ -1,23 +0,0 @@
name: kerberosio # you probably want to 'snapcraft register <name>'
base: core22 # the base snap is the execution environment for this snap
version: '3.1.0' # just for humans, typically '1.2+git' or '1.3.2'
summary: A stand-alone open source video surveillance system # 79 char long summary
description: |
Kerberos Agent is an isolated and scalable video (surveillance) management
agent made available as Open Source under the MIT License. This means that
all the source code is available for you or your company, and you can use,
transform and distribute the source code; as long you keep a reference of
the original license. Kerberos Agent can be used for commercial usage.
grade: stable # stable # must be 'stable' to release into candidate/stable channels
confinement: strict # use 'strict' once you have the right plugs and slots
environment:
GIN_MODE: release
apps:
agent:
command: main -config /var/snap/kerberosio/common
plugs: [ network, network-bind ]
parts:
agent:
source: https://github.com/kerberos-io/agent/releases/download/21c0e01/agent-amd64.tar
plugin: dump
+1 -1
View File
@@ -100,7 +100,7 @@ class App extends React.Component {
</div> </div>
)} )}
<div id="page-root"> <div id="page-root">
<Sidebar logo={logo} title="Kerberos Agent" version="v1-beta" mobile> <Sidebar logo={logo} title="Kerberos Agent" version="v3.1.1" mobile>
<Profilebar <Profilebar
username={username} username={username}
email="support@kerberos.io" email="support@kerberos.io"