# Changelog All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [v0.0.1] — 2026-05-15 Initial public release. ### Added - **Quorum-based uptime monitoring.** Multiple cooperating nodes run the same probes (HTTP, TCP, ICMP) and vote on the cluster-wide truth. A check flips state only after two consecutive aggregate evaluations agree (hysteresis), so single-node flake doesn't page anyone. - **Deterministic master election.** Among the live members of the quorum the lexicographically smallest NodeID wins — no negotiation step, no split-brain window. - **mTLS inter-node transport** with TLS 1.3 minimum, SSH-style fingerprint pinning, and a pre-shared `cluster_secret` gating the Join RPC. - **Replicated `cluster.yaml`** carrying peers, checks, and alerts. Master is the only writer; followers receive monotonic-versioned snapshots and converge on the latest. Hand-edits to the file on any node are picked up by the manual-edit watcher and forwarded through the master. - **HTTP, TCP, and ICMP probes** with configurable interval, timeout, expected status, and optional body-substring match. ICMP defaults to unprivileged UDP-mode pings so the daemon can run as a non-root user. - **SMTP and Discord alerts** with optional Go `text/template` subject/body overrides per alert, default-attach mode (`default: true`), and per-check opt-outs via `suppress_alert_ids`. - **Docker-friendly env-var configuration.** Every field in `node.yaml` can also be supplied via a `QUPTIME_*` environment variable; `qu serve` auto-initialises a fresh data volume from these on first start, so `docker compose up` is enough to launch a node. - **Interactive TUI** (`qu tui`) for peers, checks, and alerts with live refresh. - **Hardened systemd unit** shipped via `install.sh`: dedicated `quptime` user, `ProtectSystem=strict`, all capabilities dropped by default. - **Multi-arch Docker images** (`linux/amd64`, `linux/arm64`) published to `git.cer.sh/axodouble/quptime` (primary) and `ghcr.io/axodouble/quptime` (GitHub push-mirror) on every tag. - **Static Linux binaries** (`amd64`, `arm64`) published per tag with a `SHA256SUMS` file to both Gitea Releases (primary) and GitHub Releases (mirror). The official installer prefers Gitea, falls back to GitHub on failure, and verifies the checksum before placing the binary on disk. ### Security - Cluster secret is compared in constant time (`crypto/subtle.ConstantTimeCompare`). - Self-signed RSA certs minted at `qu init`; SPKI SHA-256 fingerprints are what's pinned, matching the canonical OpenSSL representation. - Private keys are written with mode `0600`; data and runtime directories with `0700`/`0750`. - All `cluster.yaml` writes go through an atomic `tmpfile + rename`. - `install.sh` downloads the published `SHA256SUMS` and refuses to install if the downloaded binary doesn't match. ### Known limitations - **Cluster-wide secret distribution.** SMTP passwords and Discord webhook URLs configured via `qu alert add …` are stored in `cluster.yaml`, which is replicated to every node. Treat every node as having read access to every alert credential. Restrict who can reach the data directory accordingly. See [docs/security.md](docs/security.md) for the threat model. - **No automatic key rotation.** Rolling a node's identity means wiping its data directory, running `qu init` again, and re-adding it from another node. - **No historical metrics.** Only the current aggregate state is kept in memory. There is no built-in graph store, SLA calculator, or audit log. - **Master-flap state.** Aggregator hysteresis state lives in memory on the current master. When leadership changes the new master starts from `StateUnknown` and re-accumulates hysteresis — expect a few seconds of delayed alerting after a master switch. - **No release signing beyond SHA256SUMS** (no cosign / GPG). Planned for a future release. [v0.0.1]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.0.1