Monitoring & Dashboards¶
This page is the map of the observability stack: what collects what, which Grafana dashboard answers which question, where coverage is strong, and the ranked roadmap of known gaps. For the collection pipeline itself (Alloy installers, Loki/Prometheus configuration, LogQL/PromQL recipes) see Logging & Metrics.
Where everything runs
Grafana (:3300), Prometheus (:9090) and Loki (:3100) are Arcane git-sync
stacks under stacks/monitoring/ on the Docker LXC (192.168.1.241, CT 101
on pve1). Dashboards are file-provisioned from
stacks/monitoring/grafana/provisioning/dashboards/ — a merge to main
redeploys them via GitOps; never import by hand. All dashboards use templated
datasource variables (${prometheus} / ${loki}), never hardcoded datasource UIDs.
Grafana is reachable at https://grafana.mdhmedia.uk (behind tinyauth SSO) — a
dashboard lives at /d/<uid>.
Stack overview¶
Six hosts have the Grafana Alloy agent installed and push telemetry to the collector
on .241: metrics via Prometheus remote_write, logs into Loki. Five report around the
clock (pve1, pve2, caddy, docker-pve1, holly); pve3 (onboarded 2026-07-25) became a
Wake-on-LAN backup node on 2026-07-30 — normally powered off, it only reports during its
evening backup window. Prometheus additionally
pull-scrapes a handful of targets: the CrowdSec agent on the Caddy LXC
(192.168.1.245:6060), the blackbox exporter (HTTPS/TCP probes of public endpoints,
Jarvis and PBS, including TLS cert expiry), smartctl_exporter on each bare-metal host,
and its own stack. Grafana sits on top of both datasources and owns alert evaluation and
delivery.
flowchart LR
subgraph Hosts[Alloy host agents]
PVE1[pve1<br/>.199]
PVE2[pve2<br/>.151]
PVE3[pve3<br/>.28 - WoL, evenings only]
CADDY[caddy CT 112<br/>.245]
D1[docker-pve1 CT 101<br/>.241]
HOLLY[holly .200]
end
subgraph Collector[Docker LXC - 192.168.1.241]
PROM[Prometheus<br/>:9090]
LOKI[Loki<br/>:3100]
BB[Blackbox<br/>exporter]
GRAF[Grafana<br/>:3300]
end
CS[CrowdSec<br/>CT 112 :6060]
WEB[Public endpoints<br/>*.mdhmedia.uk]
TG["Telegram contact point<br/>@MDHJarvis_bot - live"]
Hosts -->|metrics remote_write| PROM
Hosts -->|logs push| LOKI
PROM -->|scrape| CS
PROM -->|scrape| BB
BB -->|HTTPS probes| WEB
PROM --> GRAF
LOKI --> GRAF
GRAF -->|alerts| TG
Alloy hosts: pve1, pve2, pve3 (while awake), caddy (CT 112), docker-pve1
(CT 101), holly (Unraid; recovered 2026-07-26, Docker daemon restored 2026-07-29). Each
ships node metrics (CPU/memory/disk/network) plus systemd-journal and docker-container
logs; the Caddy host additionally parses JSON access logs with pre-indexed request_host
/ status / method labels. Since 2026-07-30 every Alloy config also enables the
netclass collector — node_network_speed_bytes reports each interface's live
negotiated link speed, feeding the NetworkLinkDegraded alert — and the Holly + Caddy
configs gained the time and stat collectors, bringing them under the
HostMetricsStale staleness alert (which keys off node_time_seconds; Holly was
invisible to it before).
Dashboard catalog¶
Twelve provisioned dashboards, layered from single-pane-of-glass down to raw logs and
grouped into Grafana folders via foldersFromFilesStructure in dashboards.yml (Start
Here / Infrastructure / Hardware & Storage / Edge & Security / Logs &
Diagnostics / Services). Homelab Command Center is the default home dashboard
(GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH), and allowUiUpdates:false keeps the git JSON
authoritative. There is one Prometheus and one Loki instance, so the datasource template
selectors are hidden (hide:2) on every board. Every data panel carries a description
(100% coverage as of the 2026-07-30 polish pass, PR #172), and two boards were renamed the
same day: Fleet Trends (was "MDH Media - Homelab Overview") and Log Explorer (was
"MDH Media - Homelab Logs") — UIDs unchanged. Open them at
https://grafana.mdhmedia.uk/d/<uid>.
| Dashboard | UID | What it answers | When to open it |
|---|---|---|---|
| Homelab Command Center | mdh-command-center |
New top-level landing board — the single pane of glass combining fleet health, alert state and endpoint status with drill-down links to everything below. | Start here. Bookmark it; every other dashboard is a drill-down from this one. |
| Fleet Trends | mdh-homelab-overview |
The fleet-trends view (renamed from "MDH Media - Homelab Overview" 2026-07-30) — per-host CPU / memory / root-disk / network from the host:* recording rules. (Alerts, endpoints/TLS, CrowdSec and the error-tail moved to the Command Center.) |
Watching fleet utilisation trends over time. |
| MDH Media - Infrastructure | mdh-infrastructure |
Everything about one host via the $host variable — uptime, CPU by mode, load, memory, filesystem %, disk I/O, network errors, plus that host's logs ($log_job). |
A specific host is slow, full, or misbehaving. |
| System Health | mdh-system-health |
Per-host CPU temperature (hwmon/coretemp), CPU busy / memory / load, and live CPU clock — bare-metal hosts (pve1/pve2/pve3) expose thermal sensors; LXCs do not. | "Is a host running hot?" under load or in a heatwave. |
| MDH Media - Security | mdh-security |
CrowdSec decisions (local vs the CAPI community list)/alerts/scenario overflows, parser throughput, and the SSH/auth audit (failed/successful logins, sudo). Edge status is summarised in the at-a-glance tiles; full traffic analytics live on Caddy Access Logs. | Suspicious traffic, an IP ban question, or an auth audit. |
| MDH Media - Ops Jobs | mdh-ops-jobs |
"Did last night's jobs run?" — vzdump backups, pve-offsite-sync to Hetzner, caddy-deploy + DDNS on CT 112, container log-error health on CT 101. |
Morning check, or after touching backups / the Caddyfile deploy. |
| MDH Media - Caddy Access Logs | mdh-caddy-logs |
Web traffic analytics: requests by status/service/method, error rates by service, 4xx/5xx log tail, Caddy service errors, full access-log browser. | A *.mdhmedia.uk route errors or you're investigating who hit what. |
| Log Explorer | mdh-homelab-logs |
A clean cross-host log explorer (renamed from "MDH Media - Homelab Logs" 2026-07-30): volume by host/job, errors & warnings over time, and a Host/Job/Search-filtered log browser. | Log-first investigations when you don't know which host to blame. |
| MDH Media - Jarvis Ops | mdh-jarvis-ops |
Jarvis (VM 121) agent liveness (blackbox-jarvis, now loaded + UP), monitoring self-health (Loki/Prometheus up), actionable firing alerts, and the neteng incident/action timeline. |
Jarvis misbehaves, or you want the monitoring-of-monitoring view. |
| Holly NAS (Unraid) | mdh-holly-nas |
Array/cache capacity and per-disk fill, a SMART-healthy rollup, Holly CPU/mem/network, per-container log volume and error rate. Per-drive SMART detail lives on Drive Health. | NAS capacity planning (only useful while Holly is powered on). |
| Drive Health (SMART) | mdh-drive-health |
New — per-drive SMART across every host with disks (pve1/pve2/pve3/holly): overall health, temperature, reallocated/pending sectors, CRC errors, SSD wear, power-on hours, one row per physical drive. | "How are my drives doing?" — the at-a-glance disk-failure early-warning board. |
| Power & Energy | mdh-power |
Homelab power draw from a pluggable per-host source model — whole-system watts via IPMI (pve2 R710) and PSU hwmon (NAS rebuild), a CPU-package RAPL proxy (pve1, understates wall draw), and HA smart plugs; plus a measured whole-system total (excludes RAPL) and a rough kWh/day. |
"How much is the homelab drawing, and costing?" — cost uses the editable Home Assistant input_number.electricity_unit_rate (24.0952 p/kWh), scraped into Prometheus with a constant fallback. |
What's on each dashboard¶
Fleet Trends (was Homelab Overview) — rows & panels
Refocused to fleet trends (2026-07-26), renamed Fleet Trends (2026-07-30): the alert, endpoint/TLS, CrowdSec and
error-tail tiles moved to the Command Center, which now owns them. Rows: At a Glance
(Hosts Reporting), Fleet (recording rules: host:*). Panels: Hosts Reporting, CPU /
Memory / Root Disk / Network by Host (all from the host:* recording rules), plus a
markdown panel linking to the Command Center and the deep-dives.
System Health — rows & panels
Rows: CPU Temperature, CPU / Memory / Load, CPU Clock Speed. Panels (all by
$host): CPU Temperature now / over time / per-core (hwmon coretemp), CPU Busy %,
Memory Used %, Load average, CPU clock avg + peak-vs-idle. Only bare-metal hosts
(pve1/pve2/pve3) expose thermal + cpufreq sensors; LXCs appear only in CPU/Mem/Load.
Infrastructure — rows & panels
Rows (all filtered by $host): Overview, CPU, Memory, Disk, Network,
Logs. Panels: Uptime, CPU Usage, Memory Usage, Root Disk Usage, Load 1m / 5m /
15m, CPU Usage by Mode, Load Averages, Filesystem Used %, Disk I/O Bytes, Disk IOPS,
Network Traffic, Network Errors / Drops, Errors / Warnings ($log_job), All Logs,
Log Volume by Job.
Security — rows & panels
Rows: Security at a Glance, CrowdSec (CT 112 — enforcing), SSH & Auth Audit. The
duplicative Caddy Edge Traffic row was removed 2026-07-26 (Caddy Access Logs owns
edge traffic analytics). Panels: CrowdSec Local Decisions (origin != CAPI — not
the ~29.6k CAPI community-list total), CrowdSec Alerts, Scenario Overflows, HTTP 4xx /
5xx (range), Failed SSH Logins, Scenario Overflows by Scenario, Parser Throughput,
Whitelist Hits by Rule, LAPI Bouncer Requests, Failed SSH Logins by Host, Failed /
Successful Logins, Sudo Usage, plus a markdown drill-down panel.
Ops Jobs — rows & panels
Rows: Backups - vzdump (pve1), Offsite Sync - pve-offsite-sync.service (pve1 → Hetzner Storage Box), Caddy Deploy & DDNS (CT 112), Container Health - docker-pve1 (CT 101). Panels: Backup Runs / Successes / Errors (range), vzdump Activity (pve1 journal), Offsite Sync Runs / Completed / Failures (range), Offsite Sync Log, Deploy Timer Runs, Caddyfile Deploys, Deploy Failures, DDNS Cron Runs (range), Recent Deploy Activity (CT 112 journal), Log Volume by Compose Project, Top 10 Containers by Error Lines, plus a markdown "Job schedules" panel with links to the Overview and Security dashboards.
Caddy Access Logs — rows & panels
Rows: Traffic Overview, Services & Hosts, Error Analysis, Caddy Service, Access Log Browser. Panels: Total Requests, 2xx / 3xx / 4xx / 5xx counts, Requests/sec, Requests by Status Code, Requests by Service/Host, Top Services, Request Methods, Status Code Distribution, Errors by Service, Error Rate Over Time, Error Logs (4xx & 5xx), Caddy Service Errors/Warnings, All Access Logs.
Log Explorer (was Homelab Logs) — rows & panels
Rebuilt 2026-07-26 into a clean log explorer; renamed Log Explorer 2026-07-30.
Rows: Overview, Errors &
Warnings, Log Browser. Panels: Total Log Lines, Active Hosts, Error Count, Warning
Count, Log Volume by Host / Job, Errors & Warnings Over Time, Errors by Host / Job, All
Logs, Error Logs Only — all honouring the Host / Job / Search variables. The dead
job="auth" stat/timeseries panels and the unit-label "Logs by Systemd Unit" panel
were removed; SSH & auth audit lives on the Security dashboard.
Jarvis Ops — rows & panels
Single row: Jarvis / neteng Ops. Panels: Jarvis agent, Firing alerts (Watchdog
excluded), Loki, Prometheus, Currently firing, Jarvis liveness, neteng incident /
action timeline. The blackbox-jarvis job is now loaded + UP and the two Jarvis
liveness panels carry or vector(0) guards, so a vanished scrape renders red not
blank; the fleet-wide error-tail panel was removed 2026-07-26 (the Command Center owns
it) to keep the board Jarvis-focused, and the empty neteng annotation layer is hidden.
Holly NAS — rows & panels
Rows: Array & Cache Capacity, Per-Disk Fill, System (CPU / Memory / Network),
Docker Containers (Loki). Panels: Parity / Redundancy Status, Array Used % / Free
(/mnt/user), Cache SSD Used % (/mnt/cache), Disks SMART-Healthy (rollup count),
Per-Disk Used %, Current Fill by Disk, CPU / Memory / Network, Container Log Volume,
Container Error Rate, Recent Container Errors. The per-drive Disk Temperatures & SMART
detail row was removed 2026-07-26 — the dedicated Drive Health (SMART) dashboard
owns cross-host per-drive SMART (label device, not the old buggy disk). The parity
banner's vector(1) is deliberate, not a stub (reviewed 2026-08-09): it is a
constant driving a value-mapping that paints a permanent red "NO PARITY DISK — ARRAY
IS UNPROTECTED" background. There is no Unraid parity metric to drive it from, and
the array is zero-parity by choice, so the banner is accurate. The one real caveat:
it is static, so if a parity disk is ever assigned, this panel must be changed by
hand or it will keep claiming the array is unprotected.
Power & Energy — sources & honesty
Power is read from whatever each host has, tagged by a source label of differing
accuracy, all landing on one gauge node_power_watts{host,source}. ipmi — pve2's
R710, in-band /dev/ipmi0 "System Level", whole-system (~160-200 W). rapl — pve1's
i3-4005U CPU-package energy (~5 W), a proxy that misses disks/fans/PSU loss; it is read
by the root-run power-metric.sh, not the node_exporter rapl collector, because the
Platypus-CVE mitigation makes /sys/.../energy_uj root-only and Alloy runs non-root.
hwmon / plug are reserved for the NAS-rebuild Corsair HX1500i PSU (native hwmon
collector) and Home-Assistant smart plugs (Matter = local). Panels: a whole-system total
that sums only ipmi|hwmon|plug (RAPL deliberately excluded — a CPU proxy in a wall total
would mislead), a rough kWh/day, the RAPL proxy on its own, per-host now, and a time series.
Cost is sourced from an editable Home Assistant helper (input_number.electricity_unit_rate,
24.0952 p/kWh) exposed via HA's prometheus integration and scraped as the homeassistant job;
the electricity_unit_rate_gbp_per_kwh recording rule reads it with a constant fallback. Edit the
rate in the HA app - the dashboard follows on the next scrape, time-versioned (a tariff change is a
step, never a retro re-cost). Wired on the production .240 HA; repoints to the new VM at the HAOS
cutover. See proxmox/power-monitoring/.
Coverage matrix¶
Honest assessment of what the stack sees today (updated 2026-07-30):
| Area | Status | Detail |
|---|---|---|
| Host metrics (node/Alloy) | Partial | 6 hosts ship Alloy remote_write node metrics (pve1, pve2, caddy/CT112, docker-pve1/CT101, holly, plus pve3 while awake — a Wake-on-LAN backup node since 2026-07-30, normally powered off) with recording rules and the Fleet Trends / Infrastructure / System Health dashboards. Holly recovered 2026-07-26 after a boot-USB failure (its Docker daemon came back after a VM reboot 2026-07-29); since PR #140 the Alloy filesystem collector excludes network fstypes (cifs\|nfs\|smb3\|fuse.*), so a dead Holly CIFS mount can no longer hang the node scrape and blank a host's node_* metrics (which had caused a pve1 + CT101 blackout while Holly was down). node_hwmon_temp_celsius + node_cpu_scaling_frequency_hertz flow from the bare-metal hosts (System Health dashboard); the netclass collector (added to every Alloy config 2026-07-30) exposes node_network_speed_bytes — live link speed, feeding the NetworkLinkDegraded alert — and remote_write silence now alerts via HostMetricsStale (time() - node_time_seconds > 300) on all five always-on hosts, the time+stat collectors having been added to Holly + Caddy to make that true. Remaining holes: CT114/116/120, VM121 (jarvis), CT122 and the HA appliance ship nothing, and node_systemd_unit_state is absent everywhere. |
| NAS / storage (Holly) | Partial | mdh-holly-nas covers array/cache/per-disk fill from node_filesystem, and SMART temp/health now flow via smartctl_exporter into the mdh-drive-health dashboard (deployed on pve1, pve2, pve3 and Holly — pve3 added 2026-07-26). Remaining: no disk-I/O panels, and Holly is excluded from vzdump (it is backed up at the Unraid level, not by Proxmox). |
| Docker containers | Good | Container logs flow per container/compose_project into Loki (job=docker-logs), and since 2026-07-27 (PR #153) cAdvisor runs on CT 101 — per-container CPU/mem/restart metrics for the whole Arcane estate (job=cadvisor, --docker_only; it works fine in the unprivileged LXC thanks to nesting=1), with ContainerRestartLooping + ContainerDownTinyauth alerting on it since PR #156. Remaining: no generic container-down rule for the other expected containers (the 2026-08-09 blackbox-lan-services tcp probes now cover the user-facing ones from the outside). |
| Reverse proxy / web (Caddy) | Good | Best-covered area: JSON access logs with pre-indexed labels (status, request_host, method) feed the Caddy Access Logs + Security dashboards, the caddy-service job catches ACME/reload errors, and all blackbox probes transit it — the only real holes are no latency (duration) panels and no client-IP/user-agent or tinyauth-denial breakdown. |
| Security / CrowdSec / auth | Partial | CrowdSec's 34 cs_* metric families are scraped from .245:6060 with a solid Security dashboard, and SSH audit panels exist. Since 2026-08-09 it finally has alert rules — CrowdSecParsingStalled and CrowdSecBouncerStalled, both alerting on work done rather than process-up (a ban-spike rule was tried and dropped — bans are normal operation, and its baseline had been measured while CrowdSec was blind), written after the gap hid a 9-day parsing outage (see the changelog). Remaining: tinyauth is probed (https via auth.mdhmedia.uk + a direct :9092 tcp probe since 2026-08-09) yet still has no panel, WireGuard's tunnel is invisible (wgadmin :10086 now tcp-probed), the SSH panels double-count auth.log+journal, and the dashboard's simulation-mode text is stale (enforcing since 2026-07-08). |
| Uptime probes / certs | Partial | 5 blackbox-http targets healthy with probe_ssl_earliest_cert_expiry + a 14-day expiry alert; the blackbox-jarvis tcp probe (VM 121 :9119) is now loaded + UP, and a blackbox-pbs tcp probe watches PBS (192.168.1.30:8007) liveness — excluded from the EndpointDown alert since 2026-07-30, because PBS is a Wake-on-LAN box that is expected to be offline outside its evening window. Since 2026-08-09 the blackbox-lan-services job tcp-probes the always-on SSO-gated/LAN services directly on their ports (tinyauth :9092, grafana :3300, audiobookrequest :8000, homepage :3080, wgadmin :10086) — probing their public hostnames is meaningless because Caddy's forward_auth 302s to auth before touching the upstream, so a parked service and a failed one look identical from outside (demonstrated 2026-08-09 by kicad + grampsweb, which are intentionally stopped and are deliberately NOT probe targets — re-add them when they return to service). Holly's apps gained a guarded job the same day (blackbox-holly-apps: audiobookshelf, qbittorrent, nzbget, radarr, sonarr, prowlarr, ombi, plex) — excluded from EndpointDown and routed to HollyAppDown, which fires only when Holly itself is demonstrably alive, so a deliberate NAS shutdown stays silent instead of emitting 8 criticals. Remaining: the icmp module is defined yet unused (no host-liveness probes). |
| Logs (Loki pipeline) | Good | 4 jobs (caddy, caddy-service, docker-logs, systemd-journal) from all Alloy hosts, ~216k lines/24h, browsable via 3 log-centric dashboards — weaknesses are consumption-side: naive keyword regexes ignoring the detected_level label, and few Loki-based alert rules (the first three — BackupJobFailed, WolPbsCycleProblem and the fleet-wide OomKillDetected — landed 2026-07-31 as Grafana-managed rules; most panel-detected conditions still don't page). |
| Backups (PBS / vzdump / offsite / HA) | Partial | PBS is the primary target, since 2026-07-30 on an evening Wake-on-LAN window: the wol-pbs container on CT 101 wakes pve3 at 19:00, the PBS job covers all 9 pve1 guests at 19:15 (prune 19:45, GC 19:50, verify 20:00, Holly's host/holly upload 20:35, then offsite sync and shutdown once PBS is idle). Since 2026-07-31 the window is alert-covered without Jarvis: PbsBackupWindowMissed (Prometheus deadman — PBS never reachable in 26h), BackupJobFailed (Loki rule on the pve1 journal's vzdump failure lines) and WolPbsCycleProblem (ERROR/WARN in the wol-pbs container log; both summaries rewritten 2026-08-04, PR #194, to be unmistakable). The Jarvis neteng-backup-watchdog cron (daily 20:05) additionally alerts if any guest's newest PBS snapshot is >30h old or missing, or the datastore exceeds 90%, and tracks the host/holly age (~23.5h at check time is healthy) — an unreachable PBS is expected (WoL box) and stays silent. The Ops Jobs dashboard still counts the local vzdump runs via brittle journal string-matches — no success/age metric, no "time since last good backup" panel — and its offsite row watches pve1's pve-offsite-sync, which was disabled 2026-07-31 (superseded by the PBS-side sync). Home Assistant's Google Drive backups are wholly unmonitored. |
| Proxmox guest level | Good | pve-exporter live since 2026-07-27 (PR #152): prompve/prometheus-pve-exporter in the prometheus stack, multi-target /pve job over all three nodes (~84 pve_up series), with ProxmoxGuestDown + ProxmoxStoragePoolFull alerting on it since PR #156 (stopped-by-design guests + the sleeping pve3 excluded). |
| Home automation (HA) | Partial | Only a blackbox https_2xx probe of ha.mdhmedia.uk. The rebuilt HAOS VM (haos, VMID 123 on pve1) is built but parked at 192.168.2.120 (stopped, onboot=0 since 2026-08-27), while the old .240 instance still owns the route. At cutover, VM 123 starts, returns to onboot=1, and takes .240 + the route; its /api/prometheus endpoint is already enabled and the monitoring-stack scrape job lands then. Until cutover no entity/automation/zigbee metrics flow, the appliance ships no host metrics, and the Google Drive backup add-on outcome is invisible. |
| Network / DNS | Missing | AdGuard (CT120) — the split-horizon DNS every LAN blackbox probe depends on — has zero telemetry (no Alloy, no dns-module probe, no exporter); WireGuard (CT116) likewise; no ICMP probes; an AdGuard outage would fail all domain probes at once with nothing pointing at DNS. |
| Alerting / notification delivery | Good | The rule set grew 2026-07-30 (PRs #169/#170): HostMetricsStale (remote_write silence), NetworkLinkDegraded (a physical link renegotiated down to <=100 Mb/s) and BlackboxExporterDown (names the root cause when every probe goes blind). HostDown now excludes the blackbox-* probe jobs (a probe target's up reflects the exporter, not the service) and the deliberately-sleeping pve3/PBS (.28/.30); EndpointDown excludes job=blackbox-pbs. The backup window gained its own rules on 2026-07-31: PbsBackupWindowMissed (Prometheus deadman) plus the first Grafana-managed Loki rules — BackupJobFailed, WolPbsCycleProblem (their summaries rewritten 2026-08-04, PR #194, so the two are unmistakable) and a fleet-wide OomKillDetected. Everything delivers to Telegram via the telegram-jarvis contact point (bot @MDHJarvis_bot; chat id hardcoded in contactpoints.yaml to dodge grafana#69950), now under a noise policy (PR #171): group_by is [alertname] only, repeats are tiered — critical 6h, warning 24h, default 12h — and the always-firing Watchdog deadman has its own 24h route (a silent Telegram for >24h is itself the signal the pipe broke). Motivating incident: the blackbox exporter died in the 2026-07-28 pve1 reboot and stayed down 43h, producing 11 false HostDown criticals (~120 Telegram messages). SMTP stays suppressed by choice; the remaining weakness is the single channel (no Alertmanager). |
| Media stack (*arr / Plex / downloads) | Missing | Zero per-app telemetry for Plex, Radarr/Sonarr/Prowlarr, qBittorrent, NZBGet, Audiobookshelf, Ombi, or AudioBookRequest (on CT 101 since 2026-07-21) — only host metrics + docker log lines, and none of the *.mdhmedia.uk media routes are probed; the MCP servers give ad-hoc queryability but nothing continuous. |
Gap roadmap¶
Ranked by leverage. Effort: S = small (config-only), M = medium (new component).
| # | Gap | Effort | Value |
|---|---|---|---|
| 1 | S | high | |
| 2 | up{} from remote_write hosts, no staleness alert)HostMetricsStale covers every always-on host |
S | high |
| 3 | Backup success monitoring — partly done 2026-07-31 (window deadman + failure-log alerts live; no success-age metric yet) | M | high |
| 4 | M | high | |
| 5 | Narrow probe coverage — largely DONE 2026-08-09 (blackbox-lan-services tcp-probes every SSO-gated/LAN service directly; Holly apps + icmp remain) |
S | high |
| 6 | M | high | |
| 7 | AdGuard DNS: partly done 2026-07-27 (dns_udp probe + AdGuardDnsDown live; still no host telemetry from CT 120) |
S | high |
| 8 | Log-based alert rules — partly done 2026-07-31 (first three Loki rules live; most panel-detected conditions still don't page) | M | high |
| 8b | S | high | |
| 9 | No SMART / temperature telemetry anywhere | M | med |
| 10 | HA, MariaDB and WireGuard essentially unobserved | M | med |
| 11 | $host |
M | med |
| 12 | Monitoring-of-monitoring: Grafana unscraped, single delivery channel | S | med |
1. Notification delivery — DONE (2026-07-13): Telegram live + Watchdog deadman — effort S, value high
What landed. The telegram-jarvis contact point now delivers to Telegram (bot
@MDHJarvis_bot). The bot token is set in the Grafana stack's Arcane project .env (TELEGRAM_BOT_TOKEN); the chat
id is hardcoded in contactpoints.yaml because grafana 13.1.2 coerces an
all-numeric $TELEGRAM_CHAT_ID back to a JSON int and crash-loops provisioning —
plain quotes and the !!str tag were both verified to still fail (grafana#69950).
The chat id is a Telegram user id, not a secret (useless without the bot token).
Deadman. An always-firing Watchdog rule (expr: vector(1)) in alerts.yml
surfaces as ALERTS{alertstate="firing"}, which the existing Grafana mirror rule
turns into a Telegram ping — since 2026-07-30 on the Watchdog's own 24h route.
No Watchdog ping in >24h means the Prometheus→Grafana→Telegram pipe is broken — the
only signal that delivery itself failed.
Noise policy (2026-07-30, PR #171). The severity-split routes landed:
policies.yaml groups by alertname only (one message covers the same alert on N
hosts) and tiers the repeats — critical 6h, warning 24h, default 12h. Still
single-channel (no Alertmanager).
2. Host-down blind spot — DONE (2026-07-30, PR #169): HostMetricsStale covers remote_write silence — effort S, value high
Was. HostDown (up==0) only covers the pull-scraped targets on .241. The real
hosts arrive via remote_write and produce no up{}, so if pve1 itself died — taking
the hypervisor, backups, and most guests with it — metrics just stopped and NOTHING
fired; dashboards showed stale green lastNotNull stats.
Now. HostMetricsStale in alerts.yml fires (warning, one series per host) when
time() - node_time_seconds > 300 — it keys off the node time collector, so all
five always-on hosts (pve1, pve2, caddy, docker-pve1, holly) are covered. The time
+ stat collectors had to be added to the Holly and Caddy Alloy configs first
(2026-07-30) — Holly was invisible to the rule before. pve3 is deliberately not
covered: it is a Wake-on-LAN backup box that is expected to be asleep. The Command
Center Last-Seen table still names which host vanished. Blackbox ICMP host-liveness
probes remain unimplemented (optional belt-and-braces).
3. Backup success monitoring: PARTIALLY CLOSED 2026-07-31 (window deadman + failure-log alerts live); HA Google Drive still unmonitored — effort M residual
What landed (2026-07-31). Three alerts now cover the nightly PBS cycle
end-to-end without Jarvis: PbsBackupWindowMissed (Prometheus — the
blackbox-pbs probe never succeeded in 26 h, i.e. the WoL window never
happened), BackupJobFailed (Grafana-managed Loki rule on the pve1
journal's vzdump failure lines, incl. could not activate storage 'pve3-pbs'
— the exact line the 2026-07-30 missed night produced; pages within minutes
of a 19:15 failure), and WolPbsCycleProblem (ERROR/WARN in the wol-pbs
container log). Together with Jarvis's 20:05 watchdog (snapshot ages via the
PBS API — since 2026-08-03 it also tracks Holly's host/holly backup age,
where ~23.5h at check time is healthy) a silent no-backup night is no longer
possible without a page. On 2026-08-04 (PR #194) the BackupJobFailed
and WolPbsCycleProblem summaries were rewritten so the two alerts are
unmistakable at a glance.
Proactive daily surface (2026-08-22). The new backup-digest Arcane
stack (stacks/management/backup-digest/) posts a once-a-day 08:00 Telegram
summary that puts the backup state in front of a human every morning -
turning silence into a signal (a firing critical had gone unread ~10 days).
It reads the PBS window + verify state time-windowed
(max_over_time(probe_success{job="blackbox-pbs"}[26h]) and the
blackbox-pbs-verify equivalent) so a WoL-asleep PBS is never misread as a
failure. The PbsVerificationFailed alert itself was reworked to the same 26h
deadman on 2026-08-25 - it previously used a bare probe_success == 0, which
fired critical all day the WoL box slept and could not be told apart from a real
verify failure; the digest still mutes it from its raw alert list only to avoid
double-reporting the authoritative windowed line above.
Built and live (PR #318, 2026-08-27). The wol-pbs orchestrator writes
pbs_last_backup_timestamp_seconds{guest=...} to a node_exporter textfile on
CT 101, and Alloy's textfile collector serves it. GuestBackupStale (48h)
alerts on per-guest backup age, while PbsBackupMetricsStale is the writer
deadman. HA's Google Drive backups remain unmonitored (expose the add-on
sensor via the HA Prometheus integration, gap 10).
Partly mitigated (2026-07-30). The Jarvis neteng-backup-watchdog cron (daily
20:05, moved from 06:00 — PBS is asleep at 06:00 now) alerts if a guest's newest PBS
snapshot is >30h old or missing, or the datastore exceeds 90%; an unreachable PBS is
expected (Wake-on-LAN box) and stays silent. vzdump / offsite / HA remain
journal-string-matched only.
4. Container metrics on docker-pve1 — DONE (2026-07-27, PR #153) — effort M, value high
What landed. cAdvisor (gcr.io/cadvisor/cadvisor, version-pinned, --docker_only)
runs in the prometheus stack on CT 101 — per-container CPU/mem/restart metrics for the
entire Arcane estate under job=cadvisor. It works in the unprivileged LXC (nesting=1
gives cgroup delegation; the pre-deploy worry was unfounded). ContainerRestartLooping
and ContainerDownTinyauth (PR #156) alert on it. Remaining nice-to-have: a generic
container-down rule for the other expected containers — partly mitigated 2026-08-09 by
the blackbox-lan-services tcp probes, which watch the user-facing services from
outside the docker engine.
5. Probe coverage is narrow (blackbox-jarvis + PBS now loaded — partly done) — effort S, value high
Done. blackbox-jarvis is now loaded + UP (the Jarvis Ops headline panels render,
and carry or vector(0) guards so a vanished scrape shows red not blank), and a new
blackbox-pbs tcp probe watches PBS (192.168.1.30:8007) — both surface on the
Command Center endpoint grid automatically.
Also done (2026-08-09). The blackbox-lan-services job tcp-probes the
always-on SSO-gated/LAN services directly on their ports: tinyauth :9092,
grafana :3300, audiobookrequest :8000, homepage :3080, wgadmin :10086
— all feeding the existing EndpointDown alert. Direct ports, not the public
hostnames, because Caddy's forward_auth answers 302 → auth before the request
reaches the upstream, so an SSO-gated https probe passes whether the backend is
dead or intentionally parked — from outside the two are indistinguishable
(kicad + grampsweb demonstrated this on 2026-08-09; both are parked on purpose, so
they are deliberately not targets — re-add each probe target when its service
returns). auth.mdhmedia.uk (https, added earlier) covers the Caddy→tinyauth path.
Still to do. The Holly apps (Plex, *arr, downloads) are unprobed, and the icmp module is defined but unused (no host-liveness probes).
6. Proxmox guest-level metrics — DONE (2026-07-27, PR #152) — effort M, value high
What landed. prometheus-pve-exporter (prompve/prometheus-pve-exporter, pinned)
in the prometheus stack, reusing the homepage PVEAuditor token (note: the real token
is homepage@pam!home); a multi-target /pve scrape job covers all three nodes
(~84 pve_up series). ProxmoxGuestDown + ProxmoxStoragePoolFull (PR #156) alert
on it, with stopped-by-design guests (100/113/115) and the WoL-sleeping pve3/PBS
excluded. The guests with no Alloy agent (CT114/116/120, VM121, CT122) are now
visible at the up/down + CPU/mem level.
7. AdGuard DNS: a zero-telemetry single point of failure that all LAN probes depend on — effort S, value high
Why. CT120 carries the split-horizon rewrites (*.mdhmedia.uk → .245) that
every LAN blackbox probe relies on. If AdGuard dies, all domain probes fail
simultaneously while the internet path stays fine — a confusing all-red storm with
nothing pointing at DNS, and LAN clients lose name resolution entirely. It currently
has no Alloy, no probe, no exporter.
How. Three layers, cheapest first: (1) done 2026-07-27 — the dns_udp module
in blackbox.yml probes 192.168.1.244:53 and AdGuardDnsDown (PR #156) names the
root cause ahead of the probe cascade; (2) run
install-alloy.sh on CT120 for host metrics + journal; (3) optionally add
adguard-exporter (ebrianne/adguard-exporter) to the monitoring stack for
query/block-rate stats. Alert on the dns probe_success with severity=critical
and mention DNS in the annotation so probe-storm triage starts at the right place.
8. Log-based alert rules: partly done 2026-07-31 — the first three Loki rules are live — effort M residual
What landed (2026-07-31). The first Grafana-managed Loki alert rules:
BackupJobFailed (vzdump failure lines in the pve1 journal),
WolPbsCycleProblem (ERROR/WARN in the wol-pbs container log — the two
summaries were rewritten 2026-08-04, PR #194, to be unmistakable) and a
fleet-wide OomKillDetected.
Why the rest still matters. The dashboards also color-code Caddy 5xx spikes, caddy-deploy rollback failures, failed-SSH bursts and CrowdSec overflows — but those still depend on a human happening to look. The systemic pattern remains: detection logic living in panel thresholds instead of alert rules.
How. Add further Grafana-managed rules (provisioning/alerting/rules.yaml,
datasource uid loki) reusing the exact panel queries: (1) offsite-sync/vzdump
failure strings from (done — mdh-ops-jobsBackupJobFailed), (2)
sum(count_over_time({job="caddy", status=~"5.."}[15m])) > 50, (3) the caddy-deploy
"failed validation|rollback" regex, (4) failed-SSH burst > threshold/10m. Also add
one Prometheus rule on CrowdSec: increase(cs_bucket_overflowed_total[1h]) > N now
that it is enforcing. They all route to the (fixed) Telegram contact point via the
existing root policy — zero new plumbing.
9. Drive SMART telemetry — DONE (2026-07-10): smartctl_exporter + Drive Health dashboard
Was. smartctl_device_* metrics were absent everywhere, so the Holly SMART panels
never rendered and pve1/pve2's disks had no failure early-warning.
Now. host-agents/install-smartctl-exporter.sh deploys the
prometheus-community smartctl_exporter
(v0.14.0) on :9633 — native systemd on PVE/Debian hosts, a privileged container on
Unraid — firewalled to the Prometheus host like the CrowdSec endpoint. A smartctl
scrape job in prometheus.yml (per-target host label) pulls it; the new
Drive Health (SMART) dashboard (mdh-drive-health) shows one row per physical drive
with health, temperature, reallocated/pending sectors, CRC, SSD wear and power-on hours,
and the Holly dashboard's SMART panels are repointed at the real metrics
(smartctl_device_smart_status, not the guessed _smart_healthy). Alerts added:
SmartHealthFailed (critical), SmartReallocatedSectors / SmartPendingSectors /
DriveTemperatureHigh > 60C (warning).
Live now: pve1, pve2, pve3 and Holly (pve2 + Holly added 2026-07-20 after the
heatwave shutdown; pve3 added 2026-07-26) — every host with disks now
reports SMART to Prometheus/Grafana. node_hwmon_temp_celsius
(host CPU/board thermals) is still absent — a separate, smaller follow-up (enable the
hwmon collector in install-alloy.sh).
10. Home Assistant, MariaDB and WireGuard have essentially zero observability — effort M, value med
Why. HA (one HTTPS probe, nothing else) runs home automation; MariaDB (CT114) serves databases with no telemetry, no exporter, not even a TCP probe; WireGuard (CT116) is the remote-access door with an admin UI that is SSO-gated but unmonitored. All three fail silently today; MariaDB and WireGuard are also invisible to any dashboard.
How. HA: the rebuilt HAOS VM is built but parked (stopped, onboot=0)
since 2026-08-27 while the old .240 instance owns the route. At cutover,
start VM 123, set onboot=1, and move .240 + the route; the VM already
exposes /api/prometheus - only the prometheus.yml scrape job remains,
planned to land at cutover - which also surfaces backup sensors for gap 3. MariaDB: add mysqld_exporter
(prom/mysqld-exporter, pinned) to the monitoring stack pointed at
192.168.1.251:3306 with a monitoring-only grant, or minimally a tcp_connect
blackbox probe of :3306. WireGuard: run install-alloy.sh on CT116 and add a
textfile metric from wg show latest-handshakes (or
prometheus_wireguard_exporter); probe wgadmin per gap 5. pve-exporter (gap 6)
provides the guest-level backstop for all three CTs meanwhile.
11. Dashboard hygiene — largely DONE (2026-07-26) — effort M, value med
What landed. One JSON PR: (1) mdh-homelab-logs rebuilt as a clean log explorer —
the dead job="auth" panels and the unit-label "Logs by Systemd Unit" panel removed,
${datasource}→${loki} renamed, schemaVersion 39; (2) mdh-homelab-overview
refocused to fleet-trends (the duplicated alert/endpoint/CrowdSec/error tiles dropped —
the Command Center owns them); (3) mdh-security's duplicative Caddy Edge Traffic row
removed (Caddy Access Logs owns edge traffic); (4) mdh-holly-nas per-drive SMART detail
removed (Drive Health owns it) and the buggy disk→device label retired with it;
(5) mdh-jarvis-ops narrowed to Jarvis-only (fleet error-tail dropped, blackbox-jarvis
guarded, empty neteng annotation hidden); (6) mdh-ops-jobs "(7d)" stat titles → "(range)";
(7) the Watchdog deadman is excluded from every Firing Alerts count and given its own
green heartbeat tile on the Command Center; (8) the Prometheus/Loki datasource selectors
are hidden fleet-wide and dashboards grouped into folders.
Still open (smaller). Replace keyword-regex error queries with
detected_level=~"error|warn" where streams carry it; add the Caddy p95 latency panel
(| json | unwrap duration, quick-win #10); dedupe mdh-caddy-logs' internal
pie-vs-timeseries / Top-Services-vs-Requests-by-Host pairs.
Dashboard overlap & dead-panel inventory (pre-2026-07-26 audit — most now fixed)
mdh-homelab-overviewduplicates itself: 3 of 6 at-a-glance tiles (Endpoints Down, Min Cert Days, Probe Latency p95) restate the Endpoints & TLS row's panels — 6 of 16 panels are blackbox-derived on a whole-homelab dashboard.mdh-security's "Caddy Edge Traffic" row substantially restatesmdh-caddy-logs: Requests by Hostname, Status Class Breakdown and 4xx/5xx by Hostname mirror the dedicated Caddy dashboard; HTTP status-class data is rendered ~8 different ways across the two dashboards plus the overview.mdh-caddy-logsduplicates internally: "Requests by Status Code" vs "Status Code Distribution" pie, "Requests by Service/Host" vs "Top Services" bargauge, and the 4xx/5xx stat tiles vs "Error Rate Over Time" all restate the same series.- Near-identical keyword-regex error-log tails appear on four dashboards (overview "Recent Errors", infrastructure "Errors/Warnings", homelab-logs "Error Logs Only", jarvis-ops "Recent homelab errors") — the jarvis-ops one scope-creeps to every host, diluting a Jarvis-focused board; the firing-alerts stat+table pair is also duplicated on overview and jarvis-ops.
- Dead panels,
mdh-homelab-logs: 5 panels query ajob="auth"that does not exist in Loki, and "Logs by Systemd Unit" filters on aunitlabel journal streams never ship (verified 0 series) — the entire Security & Authentication and Systemd Services rows are permanently blank. - Dead panels,
mdh-jarvis-ops: "Jarvis agent" stat and "Jarvis liveness" timeseries queryprobe_success{job="blackbox-jarvis"}, which returns zero series on the live Prometheus (job defined inprometheus.ymlbut never loaded) — the dashboard's two most important panels show No Data. - Dead panels,
mdh-holly-nas: both SMART panels (temperature timeseries, health table) have never had data (smartctl metrics absent from all hosts even while Holly was up). (The Parity banner'svector(1)was also listed here as a defect; on review 2026-08-09 it is intentional — see the note above.) mdh-infrastructuretriple-reports load average ("Load 1m" stat + "Load 5m/15m" stat + "Load Averages" timeseries, none with core-count thresholds) and overlapsmdh-holly-nas's System row (CPU/mem/network for host=holly) — the latter overlap is mild and arguably fine.- Inconsistent
$host/$jobvariable application makes several dashboards disagree with themselves: overview stats/alerts-table/blackbox row, caddy-logs headline stats + Top Services, and all six homelab-logs overview stats ignore the filter their sibling panels honour — selecting a host yields half-filtered views.
12. Monitoring-of-monitoring: Grafana unscraped, single-channel delivery, no scrape-health panel — effort S, value med
Why. Grafana exposes no metrics to Prometheus (no grafana_* series) and is
also the sole alert-delivery engine — if Grafana dies, alerting dies with it and
nothing notices (the up{job="prometheus"} self-scrape stat on jarvis-ops is
similarly near-dead). No dashboard shows "up by job" scrape health, so a dead
crowdsec or blackbox exporter surfaces only as frozen tiles.
How. Add a grafana scrape job (grafana:3000/metrics on monitoring-net) to
prometheus.yml; the existing HostDown (up==0) rule then covers it — though
delivery still transits Grafana, so pair it with the gap-1 deadman (missing Watchdog
pings expose a dead Grafana externally) or probe https://grafana.mdhmedia.uk per
gap 5. Add a small "Scrape Targets" stat/table panel (up by job/instance) to
mdh-homelab-overview's At a Glance row.
Quick wins¶
Cheapest changes first — most are config-only edits in this repo:
Set real Telegram credsDONE 2026-07-13 — the pipe is live (bot @MDHJarvis_bot; chat id hardcoded incontactpoints.yamlper grafana#69950) and a Watchdog deadman guards it. All 7 alert rules now deliver.Reload/redeploy Prometheus soDONE — the job is loadedblackbox-jarvisloads- UP; the
mdh-jarvis-opsliveness panels render and carryor vector(0)guards. Ablackbox-pbstcp probe (PBS192.168.1.30:8007) was added alongside. Add a staleness alertDONE 2026-07-30 —HostMetricsStale(time() - node_time_seconds > 300) inalerts.ymlcovers every always-on remote_write host; thetime+statcollectors were added to the Holly + Caddy Alloy configs so Holly is no longer invisible to it (pve3 is deliberately excluded — a Wake-on-LAN box that is expected to sleep).Add a per-host "Last Seen" table panelDONE — the Command Center's Host Last Seen (metrics) table (with a reporting-timeline state-timeline) names which remote_write host vanished, so "Hosts Reporting: 3" becomes "holly last seen 26h ago".Fix the 6 dead panels inDONE — the board was rebuilt as a clean log explorer: the deadmdh-homelab-logsjob="auth"stat/timeseries panels and theunit-label panel were removed, and SSH/auth audit now lives solely onmdh-security.- Extend blackbox target lists in
prometheus.ymlwith existing modules:tcp_connectfor tinyauth192.168.1.241:9092(the SSO SPOF), plus https targets for grafana/mdhdocs/wgadmin — config-only, exporter already running. - Add an "up by job" scrape-health stat panel to the overview's At a Glance row
(
sum by (job) (up)) — a dead crowdsec/blackbox/loki scrape currently only shows as frozen tiles. Update the dashboard's stale "simulation mode" textDONE —mdh-security's footer panel now reads "CrowdSec is enforcing (since 2026-07-08)". Still open: fix the CrowdSec Alerts stat onmdh-securityfromsum(cs_alerts)(counter-since-restart) tosum(increase(cs_alerts[$__range])).- Add "Time Since Last Successful Backup" and "Time Since Last Offsite Sync" stat
panels to
mdh-ops-jobsusing the existing journal lines (time()minus last log timestamp via a Loki instant query) — answers the #1 backup question at a glance with data already in Loki. - Add p95 latency-by-request_host panels to
mdh-caddy-logsvia{job="caddy",type="access"} | json | unwrap duration | quantile_over_time(0.95, ...)— the duration field is already in every JSON access log line.
Known quirks¶
Read before panicking at a blank panel
- Holly (Unraid NAS, VM 110) recovered 2026-07-26 after a 2026-07-25 boot-USB failure
(the USB physically dropped off the bus; reseated); the same failure had corrupted
docker.img, and the Docker daemon came back after a VM reboot on 2026-07-29. If Holly is ever down again, its node metrics, SMART rows on Drive Health, and the Holly NAS dashboard go expected-blank. - pve3 (
192.168.1.28) is a Wake-on-LAN backup node (since 2026-07-30) — normally powered off, woken daily at 19:00 for the PBS backup window and shut down once PBS is idle. Its Alloy node metrics + smartctl_exporter report only while it is awake; expected-blank the rest of the day.HostDownandHostMetricsStaledeliberately exclude it (and the PBS VM at.30). blackbox-jarvisis loaded + UP and ablackbox-pbstcp probe watches PBS (192.168.1.30:8007) — both show on the Command Center endpoint grid.blackbox-pbsis excluded from theEndpointDownalert: PBS being unreachable is normal outside the evening backup window.- A dead blackbox exporter no longer masquerades as dead sites: after the exporter
died in the 2026-07-28 pve1 reboot and sat unnoticed for 43h (11 false HostDown
criticals, ~120 Telegram messages),
HostDownignores theblackbox-*probe jobs and a dedicatedBlackboxExporterDownalert names the root cause. - Alerts now deliver to Telegram (bot @MDHJarvis_bot): the
telegram-jarviscontact point is live under a tiered noise policy (grouped by alert name only; repeats critical 6h / warning 24h / default 12h) and the Watchdog deadman pings on its own ~24h route, so a silent Telegram for over a day is itself the signal that the delivery pipe broke. - SMTP is suppressed by choice — email is intentionally not a notification channel; don't "fix" it by wiring SMTP back up. Telegram is the designated channel (now live).
References¶
- Logging & Metrics — the collection pipeline (Alloy installers, LogQL/PromQL recipes)
- Authentication — the tinyauth SSO gate in front of Grafana
- Dashboards JSON:
stacks/monitoring/grafana/provisioning/dashboards/ - Alerting provisioning:
stacks/monitoring/grafana/provisioning/alerting/ - Prometheus config:
stacks/monitoring/prometheus/