Deploy Frigate NVR stack with Mosquitto, Ntfy, and frigate-notify #190
27 changed files with 1067 additions and 0 deletions
Deploy Frigate NVR stack with Mosquitto, Ntfy, and frigate-notify
Add four new services for cloud-free camera recording and alerting: - Mosquitto MQTT broker (shared service in mqtt namespace) - Ntfy push notifications (tailnet-accessible) - Frigate NVR with GableCam via HTTP-FLV, ONNX detection, NFS recordings - frigate-notify bridging detection events to Ntfy Also adds Prometheus scrape target, Grafana dashboard, and Caddy reverse proxy entries for nvr.ops.eblu.me and ntfy.ops.eblu.me. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
commit
d0360c1585
|
|
@ -76,6 +76,12 @@ caddy_services:
|
|||
- name: cv
|
||||
host: "cv.{{ caddy_domain }}"
|
||||
backend: "https://cv.tail8d86e.ts.net"
|
||||
- name: nvr
|
||||
host: "nvr.{{ caddy_domain }}"
|
||||
backend: "https://nvr.tail8d86e.ts.net"
|
||||
- name: ntfy
|
||||
host: "ntfy.{{ caddy_domain }}"
|
||||
backend: "https://ntfy.tail8d86e.ts.net"
|
||||
|
eblume marked this conversation as resolved
|
||||
- name: sifaka
|
||||
host: "nas.{{ caddy_domain }}"
|
||||
backend: "http://sifaka:5000"
|
||||
|
|
|
|||
18
argocd/apps/frigate.yaml
Normal file
18
argocd/apps/frigate.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: frigate
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
|
||||
targetRevision: main
|
||||
path: argocd/manifests/frigate
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: frigate
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
18
argocd/apps/mosquitto.yaml
Normal file
18
argocd/apps/mosquitto.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: mosquitto
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
|
||||
targetRevision: main
|
||||
path: argocd/manifests/mosquitto
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: mqtt
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
18
argocd/apps/ntfy.yaml
Normal file
18
argocd/apps/ntfy.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ntfy
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
|
||||
targetRevision: main
|
||||
path: argocd/manifests/ntfy
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ntfy
|
||||
syncPolicy:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
32
argocd/manifests/frigate/configmap-notify.yaml
Normal file
32
argocd/manifests/frigate/configmap-notify.yaml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: frigate-notify-config
|
||||
namespace: frigate
|
||||
data:
|
||||
config.yml: |
|
||||
frigate:
|
||||
server: http://frigate:5000
|
||||
|
||||
alerts:
|
||||
general:
|
||||
title: "Frigate Alert"
|
||||
filters:
|
||||
labels:
|
||||
allow:
|
||||
- person
|
||||
- car
|
||||
- dog
|
||||
- cat
|
||||
min_score: 0.6
|
||||
|
||||
notif:
|
||||
ntfy:
|
||||
enabled: true
|
||||
server: http://ntfy.ntfy.svc.cluster.local:80
|
||||
topic: frigate-alerts
|
||||
headers: {}
|
||||
|
||||
mqtt:
|
||||
server: mosquitto.mqtt.svc.cluster.local
|
||||
port: 1883
|
||||
58
argocd/manifests/frigate/configmap.yaml
Normal file
58
argocd/manifests/frigate/configmap.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: frigate-config
|
||||
namespace: frigate
|
||||
data:
|
||||
config.yml: |
|
||||
mqtt:
|
||||
host: mosquitto.mqtt.svc.cluster.local
|
||||
port: 1883
|
||||
|
||||
go2rtc:
|
||||
streams:
|
||||
gablecam:
|
||||
- "ffmpeg:http://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159/flv?port=1935&app=bcs&stream=channel0_main.bcs#video=copy#audio=copy#audio=opus"
|
||||
|
eblume marked this conversation as resolved
eblume
commented
here and anywhere else that 192.168.1.159 appears in IaC, include a comment that the IP is reserved in UX7's DHCP config here and anywhere else that 192.168.1.159 appears in IaC, include a comment that the IP is reserved in UX7's DHCP config
|
||||
gablecam_sub:
|
||||
- "ffmpeg:http://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159/flv?port=1935&app=bcs&stream=channel0_sub.bcs"
|
||||
|
||||
cameras:
|
||||
gablecam:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://127.0.0.1:8554/gablecam
|
||||
input_args: preset-rtsp-restream
|
||||
roles: [record]
|
||||
- path: rtsp://127.0.0.1:8554/gablecam_sub
|
||||
input_args: preset-rtsp-restream
|
||||
roles: [detect]
|
||||
detect:
|
||||
enabled: true
|
||||
width: 640
|
||||
height: 480
|
||||
objects:
|
||||
track: [person, car, dog, cat]
|
||||
|
||||
detectors:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
record:
|
||||
enabled: true
|
||||
retain:
|
||||
days: 3
|
||||
mode: all
|
||||
alerts:
|
||||
retain:
|
||||
days: 30
|
||||
mode: active_objects
|
||||
detections:
|
||||
retain:
|
||||
days: 14
|
||||
mode: motion
|
||||
|
||||
snapshots:
|
||||
enabled: true
|
||||
retain:
|
||||
default: 14
|
||||
34
argocd/manifests/frigate/deployment-notify.yaml
Normal file
34
argocd/manifests/frigate/deployment-notify.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: frigate-notify
|
||||
namespace: frigate
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: frigate-notify
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: frigate-notify
|
||||
spec:
|
||||
containers:
|
||||
- name: frigate-notify
|
||||
image: ghcr.io/0x2142/frigate-notify:v0.3.5
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /app/config.yml
|
||||
subPath: config.yml
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: frigate-notify-config
|
||||
80
argocd/manifests/frigate/deployment.yaml
Normal file
80
argocd/manifests/frigate/deployment.yaml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: frigate
|
||||
namespace: frigate
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: frigate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: frigate
|
||||
spec:
|
||||
containers:
|
||||
- name: frigate
|
||||
image: ghcr.io/blakeblackshear/frigate:0.15.1
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
name: http
|
||||
- containerPort: 8554
|
||||
name: rtsp
|
||||
- containerPort: 1984
|
||||
name: go2rtc
|
||||
env:
|
||||
- name: FRIGATE_CAMERA_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: frigate-camera
|
||||
key: username
|
||||
- name: FRIGATE_CAMERA_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: frigate-camera
|
||||
key: password
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config/config.yml
|
||||
subPath: config.yml
|
||||
- name: recordings
|
||||
mountPath: /media/frigate
|
||||
- name: database
|
||||
mountPath: /db
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/version
|
||||
port: 5000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/version
|
||||
port: 5000
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: frigate-config
|
||||
- name: recordings
|
||||
persistentVolumeClaim:
|
||||
claimName: frigate-recordings
|
||||
- name: database
|
||||
persistentVolumeClaim:
|
||||
claimName: frigate-database
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 256Mi
|
||||
22
argocd/manifests/frigate/external-secret.yaml
Normal file
22
argocd/manifests/frigate/external-secret.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: frigate-camera
|
||||
namespace: frigate
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: onepassword-blumeops
|
||||
target:
|
||||
name: frigate-camera
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: username
|
||||
remoteRef:
|
||||
key: Reolink Floodlight Camera
|
||||
property: username
|
||||
- secretKey: password
|
||||
remoteRef:
|
||||
key: Reolink Floodlight Camera
|
||||
property: password
|
||||
26
argocd/manifests/frigate/ingress-tailscale.yaml
Normal file
26
argocd/manifests/frigate/ingress-tailscale.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: frigate-tailscale
|
||||
namespace: frigate
|
||||
annotations:
|
||||
tailscale.com/proxy-class: "default"
|
||||
tailscale.com/proxy-group: "ingress"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "NVR"
|
||||
gethomepage.dev/group: "Infrastructure"
|
||||
gethomepage.dev/icon: "frigate.png"
|
||||
gethomepage.dev/description: "Network video recorder"
|
||||
gethomepage.dev/href: "https://nvr.ops.eblu.me"
|
||||
gethomepage.dev/pod-selector: "app=frigate"
|
||||
spec:
|
||||
ingressClassName: tailscale
|
||||
defaultBackend:
|
||||
service:
|
||||
name: frigate
|
||||
port:
|
||||
number: 5000
|
||||
tls:
|
||||
- hosts:
|
||||
- nvr
|
||||
15
argocd/manifests/frigate/kustomization.yaml
Normal file
15
argocd/manifests/frigate/kustomization.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: frigate
|
||||
resources:
|
||||
- external-secret.yaml
|
||||
- configmap.yaml
|
||||
- configmap-notify.yaml
|
||||
- pv-nfs.yaml
|
||||
- pvc-recordings.yaml
|
||||
- pvc-database.yaml
|
||||
- deployment.yaml
|
||||
- deployment-notify.yaml
|
||||
- service.yaml
|
||||
- ingress-tailscale.yaml
|
||||
22
argocd/manifests/frigate/pv-nfs.yaml
Normal file
22
argocd/manifests/frigate/pv-nfs.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# NFS PersistentVolume for Frigate recordings
|
||||
# Requires: NFS share on sifaka at /volume1/frigate with NFS permissions for indri
|
||||
#
|
||||
# To create on Synology:
|
||||
# 1. Control Panel > Shared Folder > Create
|
||||
# 2. Name: frigate, Location: Volume 1
|
||||
# 3. Control Panel > File Services > NFS > NFS Rules
|
||||
# 4. Add rule for "frigate" share: Hostname=indri, Privilege=Read/Write, Squash=No mapping
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: frigate-recordings-nfs-pv
|
||||
spec:
|
||||
capacity:
|
||||
storage: 2Ti
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: ""
|
||||
nfs:
|
||||
server: sifaka
|
||||
path: /volume1/frigate
|
||||
|
eblume marked this conversation as resolved
eblume
commented
fyi I have created this share fyi I have created this share
|
||||
13
argocd/manifests/frigate/pvc-database.yaml
Normal file
13
argocd/manifests/frigate/pvc-database.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# PersistentVolumeClaim for Frigate SQLite database
|
||||
# Uses minikube's default storage class for local provisioning
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: frigate-database
|
||||
namespace: frigate
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
15
argocd/manifests/frigate/pvc-recordings.yaml
Normal file
15
argocd/manifests/frigate/pvc-recordings.yaml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# PersistentVolumeClaim for Frigate recordings
|
||||
# Binds to the NFS PV for sifaka:/volume1/frigate
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: frigate-recordings
|
||||
namespace: frigate
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: ""
|
||||
volumeName: frigate-recordings-nfs-pv
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Ti
|
||||
19
argocd/manifests/frigate/service.yaml
Normal file
19
argocd/manifests/frigate/service.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: frigate
|
||||
namespace: frigate
|
||||
spec:
|
||||
selector:
|
||||
app: frigate
|
||||
ports:
|
||||
- name: http
|
||||
port: 5000
|
||||
targetPort: 5000
|
||||
- name: rtsp
|
||||
port: 8554
|
||||
targetPort: 8554
|
||||
- name: go2rtc
|
||||
port: 1984
|
||||
targetPort: 1984
|
||||
|
|
@ -0,0 +1,490 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-frigate
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
frigate.json: |
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"0": { "color": "red", "index": 0, "text": "DOWN" }
|
||||
},
|
||||
"type": "value"
|
||||
},
|
||||
{
|
||||
"options": {
|
||||
"1": { "color": "green", "index": 1, "text": "UP" }
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "red", "value": null },
|
||||
{ "color": "green", "value": 1 }
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 0 },
|
||||
"id": 1,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["lastNotNull"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_service_info",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Frigate Status",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 15 },
|
||||
{ "color": "red", "value": 30 }
|
||||
]
|
||||
},
|
||||
"unit": "ms"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 0 },
|
||||
"id": 2,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["lastNotNull"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_detector_inference_speed_seconds{name=\"onnx\"} * 1000",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Detector Inference",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{ "color": "green", "value": null },
|
||||
{ "color": "yellow", "value": 5 },
|
||||
{ "color": "red", "value": 10 }
|
||||
]
|
||||
},
|
||||
"unit": "fps"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 4, "w": 4, "x": 8, "y": 0 },
|
||||
"id": 3,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["lastNotNull"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_camera_fps{camera_name=\"gablecam\"}",
|
||||
"legendFormat": "{{type}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Camera FPS",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [{ "color": "green", "value": null }]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 4, "w": 4, "x": 12, "y": 0 },
|
||||
"id": 4,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["lastNotNull"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_detection_total_fps",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Detection FPS",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": { "type": "linear" },
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": { "group": "A", "mode": "none" },
|
||||
"thresholdsStyle": { "mode": "off" }
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [{ "color": "green", "value": null }]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
|
||||
"id": 5,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": ["mean", "max"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_cpu_usage_percent",
|
||||
"legendFormat": "{{type}} - {{pid}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "CPU Usage",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": { "type": "linear" },
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": { "group": "A", "mode": "none" },
|
||||
"thresholdsStyle": { "mode": "off" }
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [{ "color": "green", "value": null }]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
|
||||
"id": 6,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": ["mean", "max"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_mem_usage_percent",
|
||||
"legendFormat": "{{type}} - {{pid}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Memory Usage",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": { "type": "linear" },
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": { "group": "A", "mode": "none" },
|
||||
"thresholdsStyle": { "mode": "off" }
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [{ "color": "green", "value": null }]
|
||||
},
|
||||
"unit": "fps"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
|
||||
"id": 7,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": ["mean", "max"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_camera_fps{camera_name=\"gablecam\"}",
|
||||
"legendFormat": "{{type}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Camera FPS Over Time",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": { "legend": false, "tooltip": false, "viz": false },
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": { "type": "linear" },
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": { "group": "A", "mode": "none" },
|
||||
"thresholdsStyle": { "mode": "off" }
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [{ "color": "green", "value": null }]
|
||||
},
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
|
||||
"id": 8,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": ["lastNotNull"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": { "mode": "multi", "sort": "desc" }
|
||||
},
|
||||
"pluginVersion": "10.0.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
||||
"expr": "frigate_storage_used_bytes",
|
||||
"legendFormat": "{{storage}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Storage Usage",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 38,
|
||||
"tags": ["frigate", "nvr", "camera"],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Frigate NVR",
|
||||
"uid": "frigate",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ resources:
|
|||
- dashboards/configmap-postgresql.yaml
|
||||
- dashboards/configmap-services.yaml
|
||||
- dashboards/configmap-zot.yaml
|
||||
- dashboards/configmap-frigate.yaml
|
||||
- dashboards/configmap-cv-apm.yaml
|
||||
- dashboards/configmap-docs-apm.yaml
|
||||
- dashboards/configmap-flyio.yaml
|
||||
|
|
|
|||
10
argocd/manifests/mosquitto/configmap.yaml
Normal file
10
argocd/manifests/mosquitto/configmap.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: mosquitto-config
|
||||
namespace: mqtt
|
||||
data:
|
||||
mosquitto.conf: |
|
||||
listener 1883
|
||||
allow_anonymous true
|
||||
persistence false
|
||||
47
argocd/manifests/mosquitto/deployment.yaml
Normal file
47
argocd/manifests/mosquitto/deployment.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mosquitto
|
||||
namespace: mqtt
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mosquitto
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mosquitto
|
||||
spec:
|
||||
containers:
|
||||
- name: mosquitto
|
||||
image: eclipse-mosquitto:2
|
||||
ports:
|
||||
- containerPort: 1883
|
||||
name: mqtt
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /mosquitto/config/mosquitto.conf
|
||||
subPath: mosquitto.conf
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 1883
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 1883
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: mosquitto-config
|
||||
8
argocd/manifests/mosquitto/kustomization.yaml
Normal file
8
argocd/manifests/mosquitto/kustomization.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: mqtt
|
||||
resources:
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
13
argocd/manifests/mosquitto/service.yaml
Normal file
13
argocd/manifests/mosquitto/service.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mosquitto
|
||||
namespace: mqtt
|
||||
spec:
|
||||
selector:
|
||||
app: mosquitto
|
||||
ports:
|
||||
- name: mqtt
|
||||
port: 1883
|
||||
targetPort: 1883
|
||||
48
argocd/manifests/ntfy/deployment.yaml
Normal file
48
argocd/manifests/ntfy/deployment.yaml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ntfy
|
||||
namespace: ntfy
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ntfy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ntfy
|
||||
spec:
|
||||
containers:
|
||||
- name: ntfy
|
||||
image: binwiederhier/ntfy:v2.11.0
|
||||
args: ["serve"]
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: cache
|
||||
mountPath: /var/cache/ntfy
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /v1/health
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /v1/health
|
||||
port: 80
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: cache
|
||||
emptyDir: {}
|
||||
26
argocd/manifests/ntfy/ingress-tailscale.yaml
Normal file
26
argocd/manifests/ntfy/ingress-tailscale.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ntfy-tailscale
|
||||
namespace: ntfy
|
||||
annotations:
|
||||
tailscale.com/proxy-class: "default"
|
||||
tailscale.com/proxy-group: "ingress"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "Ntfy"
|
||||
gethomepage.dev/group: "Infrastructure"
|
||||
|
eblume marked this conversation as resolved
eblume
commented
In this same PR, let's move ArgoCD, Prometheus, and devpi/pypi to Infastructure group. CV, Docs, TeslaMate, and Transmission should become "Services". In this same PR, let's move ArgoCD, Prometheus, and devpi/pypi to Infastructure group. CV, Docs, TeslaMate, and Transmission should become "Services".
|
||||
gethomepage.dev/icon: "ntfy.png"
|
||||
gethomepage.dev/description: "Push notifications"
|
||||
gethomepage.dev/href: "https://ntfy.ops.eblu.me"
|
||||
gethomepage.dev/pod-selector: "app=ntfy"
|
||||
spec:
|
||||
ingressClassName: tailscale
|
||||
defaultBackend:
|
||||
service:
|
||||
name: ntfy
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- ntfy
|
||||
8
argocd/manifests/ntfy/kustomization.yaml
Normal file
8
argocd/manifests/ntfy/kustomization.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: ntfy
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress-tailscale.yaml
|
||||
13
argocd/manifests/ntfy/service.yaml
Normal file
13
argocd/manifests/ntfy/service.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ntfy
|
||||
namespace: ntfy
|
||||
spec:
|
||||
selector:
|
||||
app: ntfy
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
|
|
@ -44,3 +44,9 @@ data:
|
|||
- job_name: "kube-state-metrics"
|
||||
static_configs:
|
||||
- targets: ["kube-state-metrics.monitoring.svc.cluster.local:8080"]
|
||||
|
||||
# Frigate NVR metrics
|
||||
- job_name: "frigate"
|
||||
static_configs:
|
||||
- targets: ["frigate.frigate.svc.cluster.local:5000"]
|
||||
metrics_path: /api/metrics
|
||||
|
|
|
|||
1
docs/changelog.d/deploy-frigate-nvr.feature.md
Normal file
1
docs/changelog.d/deploy-frigate-nvr.feature.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Deploy Frigate NVR stack: Frigate for camera recording/detection, Mosquitto MQTT broker, Ntfy for push notifications, and frigate-notify for detection alerting. GableCam connected via HTTP-FLV with ONNX CPU detection, NFS recordings on sifaka, and Grafana dashboard.
|
||||
Loading…
Add table
Add a link
Reference in a new issue
Why no mqtt.ops.eblu.me? There might be a reason.