Skip to content

Commit 7bc33b1

Browse files
committed
v0.0.1 release
1 parent 7b6acb2 commit 7bc33b1

7 files changed

Lines changed: 310 additions & 42 deletions

File tree

.gitignore

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Build artifacts
2+
/qu
3+
/qu-*
4+
/dist/
5+
*.exe
6+
*.test
7+
*.out
8+
9+
# Go workspace / module cache (only relevant if vendored)
10+
/vendor/
11+
12+
# Local node state — never commit anything that looks like a data dir
13+
/quptime/
14+
/etc/quptime/
15+
node.yaml
16+
cluster.yaml
17+
trust.yaml
18+
keys/
19+
20+
# Compose / secrets
21+
.env
22+
.env.local
23+
*.local.yml
24+
*.local.yaml
25+
26+
# Editor / OS scratch
27+
*.swp
28+
*.swo
29+
*~
30+
.DS_Store
31+
32+
# Test / coverage
33+
coverage.out
34+
coverage.html

CHANGELOG.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here. The format
4+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [v0.0.1] — 2026-05-15
8+
9+
Initial public release.
10+
11+
### Added
12+
13+
- **Quorum-based uptime monitoring.** Multiple cooperating nodes run
14+
the same probes (HTTP, TCP, ICMP) and vote on the cluster-wide
15+
truth. A check flips state only after two consecutive aggregate
16+
evaluations agree (hysteresis), so single-node flake doesn't page
17+
anyone.
18+
- **Deterministic master election.** Among the live members of the
19+
quorum the lexicographically smallest NodeID wins — no negotiation
20+
step, no split-brain window.
21+
- **mTLS inter-node transport** with TLS 1.3 minimum, SSH-style
22+
fingerprint pinning, and a pre-shared `cluster_secret` gating the
23+
Join RPC.
24+
- **Replicated `cluster.yaml`** carrying peers, checks, and alerts.
25+
Master is the only writer; followers receive monotonic-versioned
26+
snapshots and converge on the latest. Hand-edits to the file on any
27+
node are picked up by the manual-edit watcher and forwarded through
28+
the master.
29+
- **HTTP, TCP, and ICMP probes** with configurable interval,
30+
timeout, expected status, and optional body-substring match. ICMP
31+
defaults to unprivileged UDP-mode pings so the daemon can run as a
32+
non-root user.
33+
- **SMTP and Discord alerts** with optional Go `text/template`
34+
subject/body overrides per alert, default-attach mode (`default:
35+
true`), and per-check opt-outs via `suppress_alert_ids`.
36+
- **Docker-friendly env-var configuration.** Every field in
37+
`node.yaml` can also be supplied via a `QUPTIME_*` environment
38+
variable; `qu serve` auto-initialises a fresh data volume from
39+
these on first start, so `docker compose up` is enough to launch a
40+
node.
41+
- **Interactive TUI** (`qu tui`) for peers, checks, and alerts with
42+
live refresh.
43+
- **Hardened systemd unit** shipped via `install.sh`: dedicated
44+
`quptime` user, `ProtectSystem=strict`, all capabilities dropped by
45+
default.
46+
- **Multi-arch Docker images** (`linux/amd64`, `linux/arm64`)
47+
published to `git.cer.sh/axodouble/quptime`.
48+
- **Static Linux binaries** (`amd64`, `arm64`) published per tag with
49+
a `SHA256SUMS` file; the official installer verifies the checksum
50+
before placing the binary on disk.
51+
52+
### Security
53+
54+
- Cluster secret is compared in constant time
55+
(`crypto/subtle.ConstantTimeCompare`).
56+
- Self-signed RSA certs minted at `qu init`; SPKI SHA-256
57+
fingerprints are what's pinned, matching the canonical OpenSSL
58+
representation.
59+
- Private keys are written with mode `0600`; data and runtime
60+
directories with `0700`/`0750`.
61+
- All `cluster.yaml` writes go through an atomic `tmpfile + rename`.
62+
- `install.sh` downloads the published `SHA256SUMS` and refuses to
63+
install if the downloaded binary doesn't match.
64+
65+
### Known limitations
66+
67+
- **Cluster-wide secret distribution.** SMTP passwords and Discord
68+
webhook URLs configured via `qu alert add …` are stored in
69+
`cluster.yaml`, which is replicated to every node. Treat every node
70+
as having read access to every alert credential. Restrict who can
71+
reach the data directory accordingly. See
72+
[docs/security.md](docs/security.md) for the threat model.
73+
- **No automatic key rotation.** Rolling a node's identity means
74+
wiping its data directory, running `qu init` again, and re-adding
75+
it from another node.
76+
- **No historical metrics.** Only the current aggregate state is kept
77+
in memory. There is no built-in graph store, SLA calculator, or
78+
audit log.
79+
- **Master-flap state.** Aggregator hysteresis state lives in
80+
memory on the current master. When leadership changes the new
81+
master starts from `StateUnknown` and re-accumulates hysteresis —
82+
expect a few seconds of delayed alerting after a master switch.
83+
- **No release signing beyond SHA256SUMS** (no cosign / GPG).
84+
Planned for a future release.
85+
86+
[v0.0.1]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.0.1

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ go build -o qu ./cmd/qu
8888
To stamp the version into the binary:
8989

9090
```sh
91-
go build -ldflags "-X main.version=v0.1.0" -o qu ./cmd/qu
91+
go build -ldflags "-X main.version=v0.0.1" -o qu ./cmd/qu
9292
qu --version
9393
```
9494

@@ -100,7 +100,7 @@ amd64 and arm64, and publishes them as a Gitea release with a
100100
`SHA256SUMS` file alongside.
101101

102102
```sh
103-
git tag v0.1.0
103+
git tag v0.0.1
104104
git push --tags
105105
```
106106

@@ -166,6 +166,15 @@ c0d4... charlie.example.com:9901 true 2026-05-12T15:01:32Z
166166

167167
## Adding checks and alerts
168168

169+
> ⚠️ **Alert credentials are replicated cluster-wide.** SMTP passwords
170+
> and Discord webhook URLs live in `cluster.yaml`, which is mirrored to
171+
> every node. Any node that can read its own data directory can read
172+
> every alert secret. Treat compromising one node as compromising every
173+
> alert credential, and restrict who can reach `$QUPTIME_DIR` on each
174+
> host (the hardened systemd unit and the Docker image both default to
175+
> `0700`/`0750`). See [docs/security.md](docs/security.md) for the full
176+
> threat model.
177+
169178
```sh
170179
# alerts first so checks can reference them
171180
qu alert add discord oncall --webhook https://discord.com/api/webhooks/...

docs/deployment/docker.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ daemon can bind privileged ports and open ICMP sockets; override with
99

1010
```
1111
git.cer.sh/axodouble/quptime:master # tip of main, multi-arch
12-
git.cer.sh/axodouble/quptime:v0.1.0 # tagged release
13-
git.cer.sh/axodouble/quptime:v0.1.0-amd64 # single-arch (if you must pin)
12+
git.cer.sh/axodouble/quptime:latest # latest tagged release
13+
git.cer.sh/axodouble/quptime:v0.0.1 # specific tagged release
14+
git.cer.sh/axodouble/quptime:latest-amd64 # single-arch (if you must pin)
1415
```
1516

1617
The image embeds `QUPTIME_DIR=/etc/quptime` and declares it a volume —
@@ -24,7 +25,7 @@ For a development cluster or a single-node smoke test:
2425
# compose.yaml
2526
services:
2627
quptime:
27-
image: git.cer.sh/axodouble/quptime:v0.1.0
28+
image: git.cer.sh/axodouble/quptime:latest
2829
container_name: quptime
2930
restart: unless-stopped
3031
environment:
@@ -76,7 +77,7 @@ For local testing of the full quorum machinery without three machines:
7677
```yaml
7778
# compose.yaml
7879
x-quptime: &quptime
79-
image: git.cer.sh/axodouble/quptime:v0.1.0
80+
image: git.cer.sh/axodouble/quptime:latest
8081
restart: unless-stopped
8182
cap_add:
8283
- NET_RAW
@@ -146,7 +147,7 @@ The natural unit is one compose file per host, each running one
146147
# /etc/qu-stack/compose.yaml
147148
services:
148149
quptime:
149-
image: git.cer.sh/axodouble/quptime:v0.1.0
150+
image: git.cer.sh/axodouble/quptime:latest
150151
container_name: quptime
151152
restart: unless-stopped
152153
environment:

docs/deployment/tailscale.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ services:
5151
restart: unless-stopped
5252

5353
quptime:
54-
image: git.cer.sh/axodouble/quptime:v0.1.0
54+
image: git.cer.sh/axodouble/quptime:latest
5555
container_name: quptime
5656
environment:
5757
# host:port other QUptime nodes use to reach this one. Should be

docs/installation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ registry on every tag and every push to `master`:
7979

8080
```
8181
git.cer.sh/axodouble/quptime:master # tip of main
82-
git.cer.sh/axodouble/quptime:v0.1.0 # tagged release
82+
git.cer.sh/axodouble/quptime:latest # latest tagged release
83+
git.cer.sh/axodouble/quptime:v0.0.1 # pinned release
8384
```
8485

8586
See the [Docker deployment guide](deployment/docker.md) for compose

0 commit comments

Comments
 (0)