Compare commits

...
Sign in to create a new pull request.

8 commits

Author SHA1 Message Date
fcabbddbb8 Comment out DPI dashboard with note instead of removing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:49:12 -07:00
a39b9a4bf6 Fix UnPoller dashboard datasource UIDs, drop DPI dashboard
Upstream dashboards hardcode a different Prometheus datasource UID.
Patch them at download time to match our 'prometheus' UID. Remove the
Client DPI dashboard since DPI is not enabled on the UX7.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:48:24 -07:00
0631035a43 Add UnPoller Grafana dashboards via initcontainer
Fetches 6 Prometheus dashboards from the forge-mirrored
unpoller/dashboards repo at pod startup, matching the
TeslaMate dashboard pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:43:55 -07:00
6916c5b5ee Switch unpoller back to local API key auth
The UX7's local Integration API key works against both the new
Integration API and the classic controller API. Remote mode not needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:27:45 -07:00
61b1e0cc07 Switch unpoller to remote API mode via api.ui.com
Local API key auth doesn't work with the UX7's new Integration API.
Use the remote Site Manager API instead, which auto-discovers consoles.
Events disabled due to upstream bug (unpoller/unpoller#966).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 14:50:35 -07:00
74c8ef7209 Switch unpoller to API key auth from 1Password item 'unpoller'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 14:42:03 -07:00
8a8bfffdb1 Point unpoller kustomization at built container tag
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 14:00:56 -07:00
6b0005df1e Add UnPoller deployment for UniFi network metrics
Deploy UnPoller as a k8s service on indri to export UniFi controller
metrics to Prometheus. Custom-built container from forge mirror, with
credentials pulled from 1Password via external-secrets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 13:56:24 -07:00
12 changed files with 225 additions and 1 deletions

17
argocd/apps/unpoller.yaml Normal file
View file

@ -0,0 +1,17 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: unpoller
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/unpoller
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -90,6 +90,42 @@ spec:
volumeMounts: volumeMounts:
- name: sc-dashboard-volume - name: sc-dashboard-volume
mountPath: /tmp/dashboards mountPath: /tmp/dashboards
# Fetch UnPoller (UniFi) dashboards from forge mirror.
# Source: github.com/unpoller/dashboards (v2.0.0 Prometheus set)
- name: init-unpoller-dashboards
image: docker.io/library/alpine:kustomized
imagePullPolicy: IfNotPresent
command: ["sh", "-c"]
args:
- |
set -e
BASE_URL="https://forge.ops.eblu.me/mirrors/unpoller-dashboards/raw/branch/master/v2.0.0"
DEST="/tmp/dashboards/UniFi"
mkdir -p "$DEST"
for f in \
# DPI dashboard requires DPI enabled on both UX7 and UnPoller
# "UniFi-Poller_ Client DPI - Prometheus.json" \
"UniFi-Poller_ Client Insights - Prometheus.json" \
"UniFi-Poller_ Network Sites - Prometheus.json" \
"UniFi-Poller_ UAP Insights - Prometheus.json" \
"UniFi-Poller_ USG Insights - Prometheus.json" \
"UniFi-Poller_ USW Insights - Prometheus.json" \
; do
wget -q -O "$DEST/$f" "$BASE_URL/$(echo "$f" | sed 's/ /%20/g')"
done
# Fix datasource UIDs to match our Prometheus instance
sed -i 's/"uid": *"bdkj55oguty4gd"/"uid": "prometheus"/g' "$DEST"/*.json
sed -i 's/"uid": *"\${DS_PROMETHEUS}"/"uid": "prometheus"/g' "$DEST"/*.json
echo "Fetched $(ls "$DEST" | wc -l) UnPoller dashboards"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: sc-dashboard-volume
mountPath: /tmp/dashboards
containers: containers:
# Dashboard sidecar - watches ConfigMaps with grafana_dashboard=1 # Dashboard sidecar - watches ConfigMaps with grafana_dashboard=1
- name: grafana-sc-dashboard - name: grafana-sc-dashboard

View file

@ -72,6 +72,14 @@ scrape_configs:
- target_label: cluster - target_label: cluster
replacement: indri replacement: indri
# UniFi network metrics (via UnPoller exporter)
- job_name: "unpoller"
static_configs:
- targets: ["unpoller.monitoring.svc.cluster.local:9130"]
metric_relabel_configs:
- target_label: cluster
replacement: indri
# Frigate NVR metrics (via Caddy on indri — Frigate runs on ringtail) # Frigate NVR metrics (via Caddy on indri — Frigate runs on ringtail)
- job_name: "frigate" - job_name: "frigate"
scheme: https scheme: https

View file

@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: unpoller
namespace: monitoring
labels:
app: unpoller
spec:
replicas: 1
selector:
matchLabels:
app: unpoller
template:
metadata:
labels:
app: unpoller
spec:
containers:
- name: unpoller
image: registry.ops.eblu.me/blumeops/unpoller:kustomized
ports:
- containerPort: 9130
name: metrics
env:
- name: UP_UNIFI_DEFAULT_API_KEY
valueFrom:
secretKeyRef:
name: unpoller-unifi
key: api-key
volumeMounts:
- name: config
mountPath: /etc/unpoller
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
volumes:
- name: config
configMap:
name: unpoller-config

View file

@ -0,0 +1,18 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: unpoller-unifi
namespace: monitoring
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: unpoller-unifi
creationPolicy: Owner
data:
- secretKey: api-key
remoteRef:
key: unpoller
property: credential

View file

@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: monitoring
resources:
- deployment.yaml
- service.yaml
- external-secret.yaml
images:
- name: registry.ops.eblu.me/blumeops/unpoller
newTag: v2.34.0-6b0005d
configMapGenerator:
- name: unpoller-config
files:
- up.conf

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: unpoller
namespace: monitoring
spec:
selector:
app: unpoller
ports:
- port: 9130
targetPort: metrics
protocol: TCP
name: metrics

View file

@ -0,0 +1,16 @@
[prometheus]
http_listen = "0.0.0.0:9130"
report_errors = true
[influxdb]
disable = true
[unifi]
dynamic = false
[unifi.defaults]
# API key comes from environment variable: UP_UNIFI_DEFAULT_API_KEY
url = "https://192.168.1.1"
verify_ssl = false
save_sites = true
save_dpi = false

View file

@ -0,0 +1,40 @@
# UnPoller — UniFi metrics exporter for Prometheus
# Two-stage build: Go compilation, then minimal Alpine runtime
ARG CONTAINER_APP_VERSION=v2.34.0
FROM golang:alpine3.22 AS build
ARG CONTAINER_APP_VERSION
RUN apk add --no-cache git
RUN git clone --depth 1 --branch ${CONTAINER_APP_VERSION} \
https://forge.ops.eblu.me/mirrors/unpoller.git /app
WORKDIR /app
ENV CGO_ENABLED=0
RUN go build -ldflags="-s -w \
-X main.version=${CONTAINER_APP_VERSION} \
-X main.builtBy=blumeops \
-X golift.io/version.Version=${CONTAINER_APP_VERSION} \
-X golift.io/version.Branch=HEAD \
-X golift.io/version.BuildUser=blumeops \
-X golift.io/version.Revision=blumeops-build" \
-o /bin/unpoller .
FROM alpine:3.22
LABEL org.opencontainers.image.title="UnPoller"
LABEL org.opencontainers.image.description="UniFi metrics exporter for Prometheus"
LABEL org.opencontainers.image.source="https://github.com/unpoller/unpoller"
RUN apk add --no-cache ca-certificates tzdata
COPY --from=build /bin/unpoller /usr/bin/unpoller
EXPOSE 9130
USER 65534:65534
ENTRYPOINT ["/usr/bin/unpoller"]
CMD ["--config", "/etc/unpoller/up.conf"]

View file

@ -0,0 +1 @@
Add UnPoller deployment to monitor UniFi network metrics via Prometheus

View file

@ -1,6 +1,6 @@
--- ---
title: UniFi title: UniFi
modified: 2026-02-24 modified: 2026-03-16
tags: tags:
- infrastructure - infrastructure
- networking - networking
@ -69,6 +69,14 @@ Local admin account on the UX7. Credentials stored in 1Password (vault `blumeops
Attempted Feb 2026 with the `ubiquiti-community/unifi` Terraform provider via Pulumi. A "no-op" update on the default LAN network reset undeclared properties, bricking the network and requiring a factory reset. The provider ecosystem is too immature for single-device infrastructure. Attempted Feb 2026 with the `ubiquiti-community/unifi` Terraform provider via Pulumi. A "no-op" update on the default LAN network reset undeclared properties, bricking the network and requiring a factory reset. The provider ecosystem is too immature for single-device infrastructure.
## Monitoring
UniFi metrics are exported to Prometheus via [UnPoller](https://github.com/unpoller/unpoller), running as a k8s deployment in the `monitoring` namespace on indri. UnPoller polls the UX7 controller API using an API key and exposes metrics on port 9130.
- **Prometheus job:** `unpoller`
- **Metrics prefix:** `unifi_`
- **Credentials:** 1Password item `unpoller` (vault `blumeops`, API key)
## Related ## Related
- [[hosts]] — Device inventory - [[hosts]] — Device inventory

View file

@ -253,6 +253,13 @@ services:
upstream-source: https://code.forgejo.org/forgejo/runner/releases upstream-source: https://code.forgejo.org/forgejo/runner/releases
notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock
- name: unpoller
type: argocd
last-reviewed: 2026-03-16
current-version: "v2.34.0"
upstream-source: https://github.com/unpoller/unpoller/releases
notes: UniFi metrics exporter for Prometheus
- name: forgejo - name: forgejo
type: ansible type: ansible
last-reviewed: 2026-02-22 last-reviewed: 2026-02-22