Skip to content

Commit e713e06

Browse files
committed
Release v0.4.0: SLOs & burn-rate alerts, alert grouping, cron-aware push, DNS monitors, incidents, /metrics
- Availability SLOs: error budget on the status page, Google-SRE multi-window burn-rate alerts (budget_burn event on every channel) - Root-cause alert grouping: one notification per incident, dependents fold into their upstream's alert (alerts.group_window_secs, default 30) - Cron-aware push monitors (schedule + grace_secs), via croner - DNS monitors with answer pinning (hijack detection) - TLS public-key pinning (cert_pin), alerts once per change - Automatic incident history: /history page (Askama) + Atom feed - Prometheus /metrics, plain-text status for curl - Private monitors (public = false + server.auth_token), dual cached views - Long-term downsampling: hourly/daily buckets, bars beyond raw retention - ntfy, Gotify, Pushover channels - CLI: hora check, hora import kuma, --version - New deps: hickory-resolver, sha2 0.11, croner (deny.toml skips justified)
1 parent f221274 commit e713e06

47 files changed

Lines changed: 4905 additions & 279 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,83 @@ All notable changes to this project are documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.4.0] - 2026-06-09
9+
10+
### Added
11+
12+
- **Availability SLOs, error budgets and burn-rate alerts**: `slo_uptime = 99.9`
13+
(+ optional `slo_window_days`, default 30) per monitor. The status page and
14+
`/api/summary` show the error budget left over the window (computed from the
15+
same merged daily history as the bars, so it survives raw retention); alerts
16+
are Google-SRE multi-window burn rates - fast (2% of budget in 1h, confirmed
17+
over 5m) and slow (5% in 6h, confirmed over 30m) - via a new `budget_burn`
18+
event on every notification channel, with an exhaustion ETA. Edge-triggered:
19+
one alert per episode, re-armed when the long window cools.
20+
- **Cron-aware push monitors**: `schedule = "0 3 * * *"` (five-field cron, UTC)
21+
plus `grace_secs` (default 1800) on a push monitor alerts only when a
22+
scheduled run misses its grace window, instead of the fixed
23+
`interval_secs` gap - made for nightly jobs, à la Healthchecks.io.
24+
- **Root-cause alert grouping** (`alerts.group_window_secs`, default 30):
25+
a monitor confirmed down whose `depends_on` upstream is also down waits out
26+
the window; if the upstream alerts (or already has), the dependent's alert -
27+
and its later recovery - fold into that single notification, transitively
28+
along dependency chains. A flap inside the window sends nothing. Incident
29+
records are unaffected (history stays complete). Set 0 to disable.
30+
- **Uptime Kuma import** now also maps `json-query` monitors (JSONPath +
31+
expected value), request `headers`, `timeout`, single expected status codes,
32+
`expiryNotification = false` (→ `check_cert = false`) and Kuma groups:
33+
monitors under a Kuma folder get `group = "<folder name>"`. Both current and
34+
legacy Kuma field spellings (`maxretries`, `accepted_statuscodes`,
35+
`dns_resolve_type`) are accepted.
36+
- **DNS monitors** (`kind = "dns"`): resolve a hostname (A, AAAA, CNAME, MX, NS,
37+
TXT, SRV, SOA or PTR via `dns_record`, system or custom `dns_resolver`) and
38+
optionally **pin the expected answer** with `dns_expected` (comma-separated,
39+
order-insensitive - hijack detection that does not flap on round-robin
40+
rotation). Without `dns_expected`, any non-empty answer counts as up.
41+
- **TLS certificate pinning** (`cert_pin`, hex SHA-256 of the leaf public key):
42+
a fingerprint matching neither the pin nor the last seen value fires a
43+
`CertChanged` alert - once per change, surviving restarts, muted during
44+
maintenance windows like other alerts. The alert carries the old and new
45+
fingerprints, so a first mismatch also tells you the correct pin to configure.
46+
- **Automatic incident history**: confirmed down/up transitions are recorded as
47+
incidents (start, end, duration, error, root cause and blast radius), served
48+
on `/history` (server-rendered, no JS) and as an **Atom feed** at
49+
`/history.atom`. Incidents survive restarts: a still-open incident is
50+
re-attached on startup and closed on the first healthy tick. Closed incidents
51+
are pruned after a year.
52+
- **Prometheus `/metrics`** (text exposition format): `hora_monitor_up`,
53+
`hora_monitor_degraded`, `hora_monitor_uptime_ratio` (24h),
54+
`hora_monitor_last_latency_ms`, `hora_monitor_latency_ms{quantile=…}`
55+
(p50/p95/p99) and `hora_cert_expiry_days`, all labelled `{id, name}`.
56+
- **Private monitors**: `public = false` hides a monitor from the
57+
unauthenticated status page, `/api/summary`, latency API, badges, `/metrics`
58+
and the incident history. A viewer token (`server.auth_token`, live-reloaded,
59+
sent as `Authorization: Bearer` or `?token=`) reveals the full view; both
60+
views are cached. Config validation rejects `public = false` without a token.
61+
- **Plain-text status for terminals**: `curl status.example.com` (or an
62+
`Accept: text/plain` request) returns an aligned text rendering of the status
63+
page, groups, topology annotations and peers included.
64+
- **Long-term downsampling**: raw checks roll up into hourly buckets after
65+
7 days and daily buckets after 90, kept for a year. Buckets are written once
66+
(never recomputed from partially-pruned raw data) and the daily uptime bars
67+
transparently read them beyond the raw retention window. Aggregates of
68+
removed monitors are swept like any other orphan.
69+
- **ntfy, Gotify and Pushover** notification channels (`type = "ntfy" |
70+
"gotify" | "pushover"`), with the shared retry/redaction policy; the Gotify
71+
token travels as a header, never in the URL.
72+
- **`hora check`**: validate the configuration and exit non-zero on error
73+
(CI-friendly), and **`hora import kuma <backup.json>`**: convert an Uptime
74+
Kuma backup to Hora TOML on stdout (http/keyword, port, ping, dns and push
75+
monitors; anything else becomes a commented stub). Plus `--version`/`-V`.
76+
77+
### Changed
78+
79+
- Down alerts of monitors whose `depends_on` upstream is also down now wait
80+
out the grouping window (up to `alerts.group_window_secs`, default 30s)
81+
before being sent - or folded. Root-cause alerts are unaffected. Set
82+
`group_window_secs = 0` for the previous one-alert-per-monitor behaviour.
83+
- `/api/monitors/{id}/latency` answers 404 for private monitors without the
84+
viewer token, exactly as for unknown ids.
985

1086
## [0.3.0] - 2026-06-08
1187

0 commit comments

Comments
 (0)