Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sentinel

Uptime monitoring that fits in your head.

A status page, a dashboard, and a JSON API — with no build step, no framework, and no dependencies at all.


Why

Most self-hosted status pages arrive with a package tree, a bundler, a migration runner, and a container for the database. That's a lot of moving parts to answer one question: is the site up?

Sentinel is the whole thing in about 1,200 lines. node:http serves it, node:sqlite stores it, node:crypto secures it. npm install has nothing to install, because there is nothing to install.

git clone https://github.com/ramsai676/sentinel.git && cd sentinel

SENTINEL_PASSWORD='choose-something-long' node src/cli.ts adduser you@example.com
node src/cli.ts add "Marketing site" https://example.com 60
node src/cli.ts serve
sentinel listening on http://127.0.0.1:3000
  status page  http://127.0.0.1:3000/
  dashboard    http://127.0.0.1:3000/dashboard
  json api     http://127.0.0.1:3000/api/status

Needs Node 24+ — Node runs the TypeScript directly, so there is no compile step.


What you get

A public status page at / — no login, safe to share during an incident. Shows every monitor with uptime, average latency, a latency sparkline, and how long ago it was last checked.

A dashboard at /dashboard behind session auth, for adding and removing monitors and drilling into any one of them.

A monitor detail view with a rolled-up incident history and the raw recent checks behind it — the summary and the evidence on one page.

A JSON API at /api/status, so this can feed a CLI, a Slack bot, or someone else's dashboard:

{
  "generated_at": "2026-07-28T12:48:56.506Z",
  "monitors": [
    { "id": 1, "name": "Marketing site", "url": "https://example.com",
      "uptime": 100, "avg_latency": 111, "last_ok": 1, "total_checks": 240 }
  ]
}

Commands

sentinel serve                        Start the server and the check scheduler
sentinel adduser <email>              Create a dashboard user
sentinel add <name> <url> [interval]  Add a monitor
sentinel list                         List monitors with their uptime
sentinel check                        Run every due check once, then exit
sentinel prune [days]                 Delete checks older than N days

sentinel check exits non-zero if anything is down, so it drops straight into cron or a CI job if you'd rather not run a long-lived process:

*/5 * * * *  cd /srv/sentinel && node src/cli.ts check || notify-oncall
Variable Default
SENTINEL_DB sentinel.db Database path
SENTINEL_PORT 3000 Listen port
SENTINEL_HOST 127.0.0.1 Bind address — change to 0.0.0.0 to expose
SENTINEL_PASSWORD Password for adduser, kept out of shell history

How it works

Everything is derived from one append-only table. Checks are the only things written. Uptime, average latency, current state, and incidents are all queries over that table — so a summary can never drift out of sync with the observations behind it.

Incidents are computed, not stored. Consecutive failures collapse into a single incident with a start, an end, and a reason. An unresolved failure stays open rather than being back-dated once it recovers.

The scheduler ticks, monitors decide. Each monitor carries its own interval; the scheduler wakes every 15 seconds and runs only what's due. Checks in a tick run concurrently, so one slow endpoint doesn't delay the others.

A check is honest about failure. Timeouts abort through an AbortSignal, and DNS or connection errors are recorded with the reason rather than swallowed:

✗ Bad host — no response in 50ms (fetch failed)
✓ Example — 200 in 111ms

Security

Passwords use scrypt with a per-hash random salt, verified in constant time. A login attempt for an unknown email still runs the hash, so a bad address and a bad password take the same time and can't be told apart from outside.

Sessions are 256 bits of randomBytes, stored server-side with an expiry, and issued in an HttpOnly; SameSite=Lax cookie.

Every dynamic value is escaped on the way into the page. There's a test that puts <img src=x onerror=alert(1)> in a monitor name and asserts it never reaches the browser as markup.

It binds to localhost by default. Put it behind your own TLS terminator before changing that.


Tests

npm test

39 tests covering storage, auth, and the HTTP layer — uptime maths, the incident collapser, cascade deletes, password verification, session expiry, auth guards on every protected route, URL validation, XSS escaping, and the probe's handling of 2xx, 5xx, explicit expected statuses, and network failure.

The HTTP tests boot the real server on an ephemeral port and drive it with fetch; the probe tests inject a stub, so the suite never touches the network.

One of these tests earned its keep during development: two checks recorded in the same millisecond made ORDER BY checked_at DESC ambiguous, so a monitor could report the wrong current state. Fixed with an id tie-break.


Licence

MIT — see LICENSE.

Built by Ram Sai Kandagatla.

About

Uptime monitoring with a public status page, dashboard and JSON API. Zero dependencies — node:http, node:sqlite and node:crypto only.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages