A status page from one YAML file

yamlstatus

By June there were enough small things running at home that I couldn't tell at a glance whether they were all alive. I wanted one page that answers that.

Project
yamlstatus
Stack
TypeScript, Node, YAML
Time spent
About 4 evenings
Status
every 5 minutes

didWhat I did

Every check lives in one YAML file. There are three kinds: an HTTP request that expects a status code, a TCP connection to a port, and the age of a file. The third one exists because of the backup script, which touches a timestamp file when it succeeds.

checks:
  - name: File share
    type: tcp
    host: 192.168.1.20
    port: 445
  - name: Feed digest
    type: http
    url: http://localhost:8080/health
    expect: 200
  - name: Nightly backup
    type: file-age
    path: /var/lib/backup/last-success
    max_age: 36h

A Node script reads the file, runs every check, and writes a single static HTML page with a row per check: name, state, how long it took and when it last changed. The existing web server serves that page. No database, no daemon; a systemd timer runs the script every five minutes.

Home statusgenerated 7 Jun, 21:40
  • File sharetcp4 ms
  • Feed digesthttp 20038 ms
  • Nightly backupfile age18 h
  • Old laptoptcptimeout 5 s
Three up, one switched off. The off one now fails in five seconds instead of two minutes.

brokeWhat broke

Strings versus numbers. The HTTP check always failed for one service. I had written expect: "200" in that entry, with quotes, and the code compared it with === to a number. The loader now validates the file against a schema at startup and rejects anything it doesn't understand, with the line number, instead of guessing.

One slow check stalled everything. The checks ran one after another, and a TCP connect to a machine that was switched off has no timeout by default. One run took over two minutes. Now every check runs at once with its own five-second limit:

const CHECK_TIMEOUT_MS = 5_000

const results = await Promise.allSettled(
  checks.map((check) => runCheck(check, AbortSignal.timeout(CHECK_TIMEOUT_MS))),
)

A page that lies. When the timer itself stopped (I had broken the unit file while editing it), the last page stayed up, all green, for a day. The page now shows when it was generated, and a few lines of script on the page turn the header amber if that time is more than fifteen minutes old. A status page has to be able to say "I don't know".

nextWhat's next

  • History: keep the last 24 hours of results in a JSON file and draw a thin strip per check.
  • An RSS feed of state changes only. The morning digest from March can then tell me when something went down overnight.
  • A check type for the temperature sensor, once that project exists.