Files
esh-pfi-infrastructure/stacks/uptimekuma/README.md
T
vh 3a85a6bce1 feat(uptimekuma): rebuild on 2.5.5 as the fleet's service layer, with a client
Homepage sat dead for three days in September while every monitoring tool
reported correctly. Beszel said its host was up -- it was. Uptime Kuma was not
watching it. The dashboard fell into the seam between two working instruments.

Measured before changing anything:
  - Beszel: 18 hosts x {Status, CPU, Memory, Disk, Temperature}. Its alerts
    table is (system, name, value, min) -- there is NO url column, so it is
    structurally incapable of "this endpoint should return 200". Not a config
    gap; the data model.
  - Uptime Kuma: 6 rows, 2 of them folders. Four real monitors, all firewalls.
  - So the two are NOT redundant. They are disjoint, and the service layer
    between them was empty.

REBUILT FROM SCRATCH, operator-authorised ("uptime-kuma was never really
used... you can even dump the existing container and config"). Nothing was
migrated, which also skipped the one-way v1->v2 database migration.

  - Pinned to 2.5.5. `:latest` is a documented trap now: upstream keeps it on
    the 1.x line, so an August 2026 pull produced an image BUILT 2024-12-20
    running 1.23.16. Verified by digest -- latest and 1 share one digest while
    2/next carry 2.5.5. Pinned exactly, not floating on 2, for the same reason.
  - Moved esh-docker-vm -> ana-docker. House placement rule puts cross-site
    services beside the Beszel and Dozzle hubs, and esh-docker-vm has wedged
    unkillably twice in four months. A monitor also cannot report the failure
    of the host it runs on, so it should not share a failure domain with the
    host layer.
  - Normalised restart: always -> unless-stopped, which the 2026-08-18 README
    flagged as worth doing on the next deliberate touch.
  - UPTIME_KUMA_DB_TYPE=sqlite in the compose skips 2.x's interactive database
    screen, so the stack comes up ready rather than parked on a form.

scripts/kuma is a first-party Socket.IO client, because both obvious paths are
wrong: there is no REST CRUD API in EITHER major version (server/routers/ holds
exactly two files, /metrics + badges + status pages), and the community wrapper
uptime-kuma-api is abandoned -- last release 2023-09-26, ceiling 1.23.1, no 2.x
support ever.

  ⚠ getMonitorList's callback returns only {ok:true}; the list arrives as a
  SEPARATE pushed monitorList event. Reading the ack yields an empty board that
  looks authoritative -- which duplicated all 13 rows on the first re-seed
  before the bug was found. The client now waits for the push, and carries a
  dedupe verb because of it.

13 monitors seeded from monitors.yaml, keyed on name so a re-run updates rather
than forking the board -- proven by re-running it (0 added, 13 updated), not
assumed. Every URL was probed before being written: all 200. A board that ships
red teaches everyone to ignore it.

Verified: 13 rows, no duplicates, all UP with "200 - OK" read from the database
WITH its WAL (a first read of kuma.db alone showed a stale 26 -- the copy
predated the deletes). Homepage renders exactly one Uptime Kuma card.

⚠ NOT YET WIRED: notification delivery. The board detects but tells nobody,
which is the same gap this work exists to close. The beszel-althing bridge
hardcodes a [Beszel] subject prefix and hub footer, so routing Kuma through it
unchanged would mislabel the alerts. Needs a decision before it is generalised.
2026-09-21 22:59:37 -07:00

88 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# uptimekuma — the fleet's SERVICE-layer monitor (ana-docker)
`louislam/uptime-kuma:2.5.5` on **ana-docker** (`10.250.50.70:3001`), beside the
Beszel and Dozzle hubs.
## Why it exists — the lane split
Measured 2026-09-21, after the fleet dashboard sat dead for three days while
every monitoring tool reported correctly:
| tool | what it asks | scope |
|---|---|---|
| **Beszel** | is the box alive, and is it out of CPU / memory / disk / heat | 18 hosts × {Status, CPU, Memory, Disk, Temperature} |
| **Uptime Kuma** (this) | **is the service actually serving** | the fleet's user-facing endpoints |
| **Homepage** | — | **DISPLAY ONLY.** Polls 38 URLs, alerts nobody. |
Beszel and Kuma are **not redundant** — they are disjoint, and the seam between
them is where things die quietly. Beszel's alerts bind to a *system* with a
threshold (`alerts` table: `system, name, value, min`); there is **no URL
column**, so it is structurally incapable of "this endpoint should return 200".
That is not a configuration gap, it is the data model.
On 2026-09-18 `homepage` wedged in an unkillable D-state. Beszel reported
`esh-vm-docker: up` — correctly; the host *was* up. Kuma was not watching it.
The dead dashboard fell between two working instruments and stayed dark for
three days until the operator hit a 404.
## Rebuilt from scratch, 2026-09-21
Operator-authorised: *"uptime-kuma was never really used… you can even dump the
existing container and config and start over from scratch."* The prior instance
had four monitors — three firewall pings and one HTTPS check — and nothing was
migrated. This also skipped the one-way v1→v2 database migration entirely.
Two things changed with the rebuild:
**Pinned to `2.5.5`, and `:latest` is now a documented trap.** Upstream keeps
`latest` on the **1.x** line: an August 2026 pull produced an image *built
2024-12-20* running 1.23.16. Verified by digest — `latest` and `1` resolve to
the same image, while `2`/`next` carry 2.5.5. 2.x has been stable since 2.2.0
(2026-03-05), twelve releases, zero prereleases. Pinned exactly rather than
floating on `2`, for the same reason `latest` burned us.
**Moved esh-docker-vm → ana-docker.** House placement rule (CLAUDE.md): *cross-
site services live on ana-docker and pull from agents on the other hosts* — a
fleet-wide service monitor is exactly that. And `esh-docker-vm` has wedged
unkillably twice in four months (2026-06-03, 2026-09-18), so it is the worst
box in the fleet to host the thing that would tell us. A monitor also cannot
report the failure of the host it runs on; Beszel covers that layer, and the
two should not share a failure domain.
Normalised at the same time: `restart: always` → `unless-stopped`, which the
2026-08-18 README flagged as *"worth normalising on the next deliberate touch"*.
`always` revives a container that was stopped on purpose.
## Scripting it
**There is no REST CRUD API — in either major version.** Checked against the
2.5.5 source tree, not the docs: `server/routers/` contains exactly two files,
`api-router.js` (Prometheus `/metrics`, badges, entry page) and
`status-page-router.js`. API keys unlock `/metrics` and badges only.
Automation goes over **Socket.IO**, the same channel the web UI uses;
`server/socket-handlers/general-socket-handler.js` carries `add`,
`editMonitor`, `deleteMonitor`, `getMonitorList`.
⚠️ **Do not reach for `uptime-kuma-api`** (the Python wrapper). It is abandoned
— last release 2023-09-26 — and its stated ceiling is *"support for uptime kuma
1.23.0 and 1.23.1"*. It does not support 2.x and never will. Use a thin
first-party Socket.IO client instead.
## The Homepage widget is deliberately absent
The compose carries **no `homepage.widget.*` labels**. The widget needs a
published status-page slug, and a from-scratch install has none — it would poll
a 404 forever. That matters more here than elsewhere: homepage widgets pointed
at dead targets are the suspected mechanism behind *both* of that dashboard's
unkillable wedges (see `incident_esh_docker_nfs_boot_race`, 2026-06-03 entry).
Re-add `homepage.widget.type` / `.url` / `.slug` only once the status page
actually exists. Label changes need `docker compose up -d`, not `restart`.
## Data
Named volume `uptimekuma_uptime-kuma` → `/app/data` holds every monitor
definition and all history. Recreating the container is safe; deleting that
volume is not.