Deploy Frigate NVR stack with Mosquitto, Ntfy, and frigate-notify (#190)
## Summary Deploy a cloud-free NVR stack for the GableCam (ReoLink Elite Floodlight at 192.168.1.159): - **Mosquitto** — shared MQTT broker in `mqtt` namespace (cluster-internal, no auth) - **Ntfy** — self-hosted push notifications in `ntfy` namespace, exposed at `ntfy.tail8d86e.ts.net` / `ntfy.ops.eblu.me` - **Frigate** — NVR with GableCam via HTTP-FLV, ONNX CPU detection, NFS recordings on sifaka, exposed at `nvr.tail8d86e.ts.net` / `nvr.ops.eblu.me` - **frigate-notify** — bridges Frigate detection events (person, car, dog, cat) to Ntfy alerts via MQTT Also includes: - Prometheus scrape target for Frigate metrics - Grafana dashboard for Frigate (status, inference speed, FPS, CPU/memory, storage) - Caddy reverse proxy entries for `nvr.ops.eblu.me` and `ntfy.ops.eblu.me` ## Prerequisites - [ ] Create NFS share `frigate` on sifaka (`/volume1/frigate`, RW for indri) - [ ] Create 1Password item "Reolink Floodlight Camera" in `blumeops` vault with `username` and `password` fields ## Deployment (after merge) ```bash argocd app sync apps argocd app sync mosquitto argocd app sync ntfy argocd app sync frigate argocd app sync grafana-config argocd app sync prometheus mise run provision-indri -- --tags caddy mise run services-check ``` ## Verification - [ ] Mosquitto pod running, accepting connections on 1883 - [ ] Ntfy web UI accessible at `ntfy.ops.eblu.me` - [ ] Frigate web UI at `nvr.ops.eblu.me` showing GableCam live feed - [ ] Object detection working (ONNX, person/car/dog/cat) - [ ] Recordings appearing in NFS share on sifaka - [ ] frigate-notify sending detection alerts to Ntfy - [ ] Prometheus scraping Frigate metrics - [ ] Grafana dashboard showing Frigate data Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/190
This commit is contained in:
parent
2fad37f500
commit
04c7f3c45a
39 changed files with 1136 additions and 27 deletions
|
|
@ -67,7 +67,7 @@ caddy_services:
|
|||
- name: navidrome
|
||||
host: "dj.{{ caddy_domain }}"
|
||||
backend: "https://dj.tail8d86e.ts.net"
|
||||
- name: hajimari
|
||||
- name: homepage
|
||||
host: "go.{{ caddy_domain }}"
|
||||
backend: "https://go.tail8d86e.ts.net"
|
||||
- name: docs
|
||||
|
|
@ -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"
|
||||
- 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
|
||||
|
|
@ -14,7 +14,7 @@ metadata:
|
|||
tailscale.com/proxy-group: "ingress"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "ArgoCD"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Infrastructure"
|
||||
gethomepage.dev/icon: "argo-cd.png"
|
||||
gethomepage.dev/description: "GitOps CD"
|
||||
gethomepage.dev/href: "https://argocd.ops.eblu.me"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ metadata:
|
|||
tailscale.com/tags: "tag:k8s,tag:flyio-target"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "CV"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Services"
|
||||
gethomepage.dev/icon: "mdi-file-document"
|
||||
gethomepage.dev/description: "Resume / CV"
|
||||
gethomepage.dev/href: "https://cv.eblu.me"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ metadata:
|
|||
tailscale.com/proxy-group: "ingress"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "PyPI"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Infrastructure"
|
||||
gethomepage.dev/icon: "pypi.png"
|
||||
gethomepage.dev/description: "PyPI cache"
|
||||
gethomepage.dev/href: "https://pypi.ops.eblu.me"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ metadata:
|
|||
tailscale.com/tags: "tag:k8s,tag:flyio-target"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "Docs"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Services"
|
||||
gethomepage.dev/icon: "mdi-book-open-page-variant"
|
||||
gethomepage.dev/description: "BlumeOps Documentation"
|
||||
gethomepage.dev/href: "https://docs.eblu.me"
|
||||
|
|
|
|||
31
argocd/manifests/frigate/configmap-notify.yaml
Normal file
31
argocd/manifests/frigate/configmap-notify.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: frigate-notify-config
|
||||
namespace: frigate
|
||||
data:
|
||||
config.yml: |
|
||||
frigate:
|
||||
server: http://frigate:5000
|
||||
public_url: https://nvr.ops.eblu.me
|
||||
|
||||
webapi:
|
||||
enabled: false
|
||||
|
||||
mqtt:
|
||||
enabled: true
|
||||
server: mosquitto.mqtt.svc.cluster.local
|
||||
port: 1883
|
||||
clientid: frigate-notify
|
||||
topic_prefix: frigate
|
||||
|
||||
alerts:
|
||||
general:
|
||||
title: "Frigate Alert"
|
||||
|
||||
ntfy:
|
||||
enabled: true
|
||||
server: http://ntfy.ntfy.svc.cluster.local:80
|
||||
topic: frigate-alerts
|
||||
headers:
|
||||
- X-Actions: "view, Open Event, {{.Extra.PublicURL}}/review?id={{.ID}}, clear=true; view, Open Camera, {{.Extra.PublicURL}}/#/cameras/{{.Camera}}"
|
||||
71
argocd/manifests/frigate/configmap.yaml
Normal file
71
argocd/manifests/frigate/configmap.yaml
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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 IP is reserved in UX7 DHCP config
|
||||
gablecam:
|
||||
- "rtsp://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159:554/h264Preview_01_main"
|
||||
gablecam_sub:
|
||||
- "rtsp://{FRIGATE_CAMERA_USER}:{FRIGATE_CAMERA_PASSWORD}@192.168.1.159:554/h264Preview_01_sub"
|
||||
|
||||
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
|
||||
stationary:
|
||||
max_frames:
|
||||
default: 1500
|
||||
objects:
|
||||
car: 150
|
||||
objects:
|
||||
track: [person, car, dog, cat, bird]
|
||||
|
||||
detectors:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
model:
|
||||
model_type: yolonas
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_pixel_format: bgr
|
||||
path: /media/frigate/models/yolo_nas_s.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
|
||||
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.16.4-standard-arm64
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -199,5 +199,7 @@ config:
|
|||
style: column
|
||||
Content:
|
||||
style: column
|
||||
Misc:
|
||||
Infrastructure:
|
||||
style: column
|
||||
Services:
|
||||
style: column
|
||||
|
|
|
|||
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
|
||||
13
argocd/manifests/ntfy/configmap.yaml
Normal file
13
argocd/manifests/ntfy/configmap.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ntfy-config
|
||||
namespace: ntfy
|
||||
data:
|
||||
server.yml: |
|
||||
base-url: https://ntfy.ops.eblu.me
|
||||
upstream-base-url: https://ntfy.sh
|
||||
attachment-cache-dir: /var/cache/ntfy/attachments
|
||||
attachment-total-size-limit: 1G
|
||||
attachment-file-size-limit: 10M
|
||||
attachment-expiry-duration: 24h
|
||||
54
argocd/manifests/ntfy/deployment.yaml
Normal file
54
argocd/manifests/ntfy/deployment.yaml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
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", "--config", "/etc/ntfy/server.yml"]
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/ntfy/server.yml
|
||||
subPath: server.yml
|
||||
- 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: config
|
||||
configMap:
|
||||
name: ntfy-config
|
||||
- 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"
|
||||
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
|
||||
9
argocd/manifests/ntfy/kustomization.yaml
Normal file
9
argocd/manifests/ntfy/kustomization.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: ntfy
|
||||
resources:
|
||||
- configmap.yaml
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ metadata:
|
|||
tailscale.com/tags: "tag:k8s,tag:flyio-target"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "Prometheus"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Infrastructure"
|
||||
gethomepage.dev/icon: "prometheus.png"
|
||||
gethomepage.dev/description: "Metrics storage"
|
||||
gethomepage.dev/href: "https://prometheus.ops.eblu.me"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ metadata:
|
|||
tailscale.com/proxy-group: "ingress"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "TeslaMate"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Services"
|
||||
gethomepage.dev/icon: "teslamate.png"
|
||||
gethomepage.dev/description: "Tesla data logger"
|
||||
gethomepage.dev/href: "https://tesla.ops.eblu.me"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ metadata:
|
|||
tailscale.com/proxy-group: "ingress"
|
||||
gethomepage.dev/enabled: "true"
|
||||
gethomepage.dev/name: "Transmission"
|
||||
gethomepage.dev/group: "Misc"
|
||||
gethomepage.dev/group: "Services"
|
||||
gethomepage.dev/icon: "transmission.png"
|
||||
gethomepage.dev/description: "Torrent client"
|
||||
gethomepage.dev/href: "https://torrent.ops.eblu.me"
|
||||
|
|
|
|||
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 cloud-free NVR stack: Frigate 0.16.4 (ARM64) with ONNX/YOLO-NAS-s detection, Mosquitto MQTT broker, Ntfy self-hosted push notifications (with iOS APNs relay), and frigate-notify for detection alerting. GableCam (ReoLink Elite Floodlight) connected via RTSP with NFS recordings on sifaka, Grafana dashboard, Prometheus scraping, Homepage integration, and Caddy reverse proxies at nvr.ops.eblu.me and ntfy.ops.eblu.me.
|
||||
|
|
@ -14,3 +14,4 @@ Plans that have been fully implemented and verified. Kept for historical referen
|
|||
|------|-----------|-------------|
|
||||
| [[adopt-dagger-ci]] | 2026-02-11 | Adopt Dagger as CI/CD build engine (Phases 1–3) |
|
||||
| [[segment-home-network]] | 2026-02-14 | Manual three-network segmentation for UniFi Express 7 |
|
||||
| [[operationalize-reolink-camera]] | 2026-02-15 | Deploy Frigate NVR stack with Mosquitto, Ntfy, and frigate-notify |
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ tags:
|
|||
|
||||
# Plan: Operationalize ReoLink Camera
|
||||
|
||||
> **Status:** Planned (not yet executed)
|
||||
> **Status:** Completed (2026-02-15)
|
||||
> **Depends on:** [[add-unifi-pulumi-stack]] — the camera must be on the IoT VLAN, isolated from the rest of the network.
|
||||
> **PR:** #190
|
||||
|
||||
## Background
|
||||
|
||||
|
|
@ -241,23 +242,23 @@ Camera settings to apply: enable RTSP and ONVIF, set "fluency first" encoding mo
|
|||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Camera streams accessible via RTSP from services subnet
|
||||
- [ ] Camera has no internet access (blocked at firewall)
|
||||
- [ ] Frigate pod is running and showing live camera feed in web UI
|
||||
- [ ] Recordings appearing in NFS share on sifaka
|
||||
- [ ] Object detection working (person/vehicle detected in Frigate UI)
|
||||
- [ ] Retention policy active (old recordings cleaned up automatically)
|
||||
- [ ] Alerts firing on detection events
|
||||
- [ ] Prometheus metrics visible in Grafana dashboard
|
||||
- [ ] `mise run services-check` passes
|
||||
- [x] Camera streams accessible via RTSP from services subnet
|
||||
- [ ] Camera has no internet access (blocked at firewall) — pending IoT VLAN segmentation
|
||||
- [x] Frigate pod is running and showing live camera feed in web UI
|
||||
- [x] Recordings appearing in NFS share on sifaka
|
||||
- [x] Object detection working (person/vehicle detected in Frigate UI)
|
||||
- [x] Retention policy active (old recordings cleaned up automatically)
|
||||
- [x] Alerts firing on detection events (ntfy push notifications with ~6s delivery)
|
||||
- [x] Prometheus metrics visible in Grafana dashboard
|
||||
- [x] `mise run services-check` passes
|
||||
|
||||
## Open Questions
|
||||
## Open Questions (Resolved)
|
||||
|
||||
- **MQTT broker:** Is there an existing MQTT broker in the cluster, or does one need to be deployed? Mosquitto is lightweight and standard.
|
||||
- **Home Assistant:** Frigate works standalone, but HA adds richer automation (e.g., turn on floodlight when person detected, arm/disarm by time of day). Evaluate whether to add HA as a future plan.
|
||||
- **Sifaka NFS share sizing:** How much space to allocate on the NAS? Start with 2 TB and monitor. The hybrid retention strategy keeps this manageable.
|
||||
- **Additional cameras:** If more cameras are added later, CPU detection may become a bottleneck. At that point, evaluate a Hailo-8L USB accelerator or a dedicated Frigate host (e.g., RPi5).
|
||||
- **Floodlight automation:** The ReoLink HTTP API supports floodlight control. Could be automated to turn on when Frigate detects a person at night — but this requires either HA or a custom script listening to MQTT events.
|
||||
- **MQTT broker:** Deployed Mosquitto (eclipse-mosquitto:2) in the `mqtt` namespace. Lightweight, anonymous access, cluster-internal only (no Caddy/ingress needed since MQTT is TCP, not HTTP).
|
||||
- **Home Assistant:** Deferred. Frigate + frigate-notify + ntfy provides a complete pipeline without HA.
|
||||
- **Sifaka NFS share sizing:** Allocated 2 TB. Hybrid retention (3d continuous, 30d alerts, 14d detections) keeps usage well within bounds.
|
||||
- **Additional cameras:** Using ONNX/YOLO-NAS-s on CPU at ~535ms/frame, ~2 FPS detection. Adequate for single camera. Apple Silicon Detector (ASD) via ZMQ is the next upgrade path for better performance (~15ms via Neural Engine). Requires Frigate 0.17+.
|
||||
- **Floodlight automation:** Deferred to future Home Assistant evaluation.
|
||||
|
||||
## Future Considerations
|
||||
|
||||
|
|
@ -80,6 +80,8 @@ check_http "Transmission" "https://torrent.ops.eblu.me/"
|
|||
check_http "Immich" "https://photos.ops.eblu.me/"
|
||||
check_http "Navidrome" "https://dj.ops.eblu.me/"
|
||||
check_http "CV" "https://cv.ops.eblu.me/"
|
||||
check_http "Ntfy" "https://ntfy.ops.eblu.me/v1/health"
|
||||
check_http "Frigate" "https://nvr.ops.eblu.me/api/version"
|
||||
|
||||
echo ""
|
||||
echo "Public services (via Fly.io):"
|
||||
|
|
@ -99,6 +101,10 @@ check_service "grafana" "kubectl --context=minikube-indri -n monitoring get pods
|
|||
check_service "miniflux" "kubectl --context=minikube-indri -n miniflux get pods -l app=miniflux -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
check_service "teslamate" "kubectl --context=minikube-indri -n teslamate get pods -l app=teslamate -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
check_service "blumeops-pg" "kubectl --context=minikube-indri -n databases get pods -l cnpg.io/cluster=blumeops-pg -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
check_service "mosquitto" "kubectl --context=minikube-indri -n mqtt get pods -l app=mosquitto -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
check_service "ntfy" "kubectl --context=minikube-indri -n ntfy get pods -l app=ntfy -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
check_service "frigate" "kubectl --context=minikube-indri -n frigate get pods -l app=frigate -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
check_service "frigate-notify" "kubectl --context=minikube-indri -n frigate get pods -l app=frigate-notify -o jsonpath='{.items[0].status.phase}' | grep -q Running"
|
||||
|
||||
echo ""
|
||||
echo "ArgoCD app sync status:"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue