NPM stays the source of truth for which domains exist; this adds an optional enrichment layer on top of the existing NPM_INCLUDE_PATTERNS filtering, gated behind ADMIN_PASSWORD (disabled entirely if unset). - Force-show/force-hide any NPM-discovered host regardless of what the include pattern would otherwise decide - Per-host display name and check path overrides - Manually add targets that aren't proxied through NPM at all - Every override falls back to the NPM-derived default when unset — a host with no override behaves exactly as before this existed Host overrides and manual targets live in SQLite (better-sqlite3, WAL mode) and are re-read on every 60s check tick, so admin changes take effect within one cycle with no restart. NPM host discovery keeps its original 10-minute cadence — only the local override reads happen every tick. The core merge (NPM hosts + overrides + manual targets -> final check list) is a pure function in targets.ts, kept separate from the checker's I/O for testability. Verified end-to-end against a local mock NPM server (auth, force-show/hide, rename, custom check path, manual targets, reset-to-default all propagate correctly) and against the actual Docker build/runtime (better-sqlite3's native addon builds and loads correctly in the Alpine image). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
109 lines
4.6 KiB
Markdown
109 lines
4.6 KiB
Markdown
# status
|
|
|
|
A lightweight HTTP status checker that sources its target list from Nginx Proxy Manager and serves results as JSON and a terminal-aesthetic HTML page.
|
|
|
|
Designed to run as a standalone public status page on its own subdomain — if your main site is down, the status page is still reachable independently.
|
|
|
|
## How it works
|
|
|
|
1. On startup (and every 10 minutes), fetches all enabled proxy hosts from the NPM API
|
|
2. Filters to domains matching `NPM_INCLUDE_PATTERNS` — unless overridden per-host in the admin UI (see below)
|
|
3. HTTP-checks each host every 60 seconds
|
|
4. Serves current results at `/` (HTML) and `/api/status` (JSON)
|
|
|
|
The HTML page auto-refreshes every 60 seconds.
|
|
|
|
## Admin UI
|
|
|
|
NPM stays the source of truth for *which domains exist*. The optional admin UI at `/admin` lets you override how they're presented, without touching NPM itself:
|
|
|
|
- **Force-show or force-hide** any NPM-discovered host, regardless of what `NPM_INCLUDE_PATTERNS` would otherwise decide
|
|
- **Rename** a host's display name (e.g. "Wisp" instead of `wisp.dev.xplwd.com`)
|
|
- **Custom check path** per host, for anything whose root path isn't a meaningful health check
|
|
- **Manually add non-NPM targets** — anything not proxied through NPM (a raw-port service, or a third-party dependency you want on the same page), checked exactly like a discovered host
|
|
|
|
Every override is enrichment, not replacement: leave a field blank and it falls back to the NPM-derived default (domain name, root path, pattern-match result). A host removed from NPM entirely just stops appearing — its override row, if any, becomes inert rather than erroring.
|
|
|
|
Overrides are stored in SQLite (`DATA_DIR/statuspage.db`) and re-read on every 60-second check tick, so changes in the admin UI take effect within one cycle — no restart needed. The 10-minute NPM host-discovery refresh is unaffected; only the local override/manual-target reads happen every tick.
|
|
|
|
Set `ADMIN_PASSWORD` to enable `/admin` and its API (`/api/admin/*`, gated by an `X-Admin-Password` header, same stateless pattern as `wisp`'s upload gate). Leave it unset and `/admin` is disabled outright — the main status page and `/api/status` work exactly as before, unaffected.
|
|
|
|
## Running locally
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
# fill in NPM_EMAIL and NPM_SECRET
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| `PORT` | `3035` | Port to listen on |
|
|
| `NPM_BASE_URL` | `http://localhost:81` | Base URL of your Nginx Proxy Manager instance |
|
|
| `NPM_EMAIL` | — | NPM account email |
|
|
| `NPM_SECRET` | — | NPM account password |
|
|
| `NPM_INCLUDE_PATTERNS` | `\.example\.com$` | Comma-separated regex patterns; only domains matching at least one are checked by default |
|
|
| `ADMIN_PASSWORD` | — | Gates `/admin`. Unset disables the admin UI entirely |
|
|
| `DATA_DIR` | `./data` | Where the SQLite DB (host overrides, manual targets) lives |
|
|
|
|
## Docker
|
|
|
|
```bash
|
|
docker compose up -d
|
|
```
|
|
|
|
Uses the image from `IMAGE` in `.env`. For a local build:
|
|
|
|
```bash
|
|
docker build -t status:local .
|
|
IMAGE=status:local docker compose up -d
|
|
```
|
|
|
|
## CI/CD
|
|
|
|
Gitea Actions workflow at `.gitea/workflows/build.yml`. Builds and pushes to the Gitea container registry on every push to `main`. Requires a `TKNTKN` secret (PAT with `packages:write`) in the repo settings.
|
|
|
|
## API
|
|
|
|
```
|
|
GET /api/status
|
|
```
|
|
|
|
```json
|
|
{
|
|
"lastRefreshed": "2026-07-07T19:35:43Z",
|
|
"hosts": [
|
|
{
|
|
"name": "example.com",
|
|
"url": "https://example.com",
|
|
"status": "up",
|
|
"statusCode": 200,
|
|
"latencyMs": 182,
|
|
"checkedAt": "2026-07-07T19:35:43Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
`status` is `"up"` for any response (including 4xx — the server is reachable), `"down"` for connection errors and 5xx, `"unknown"` before the first check completes.
|
|
|
|
### Admin API
|
|
|
|
All routes below `/api/admin` (except `/auth/check`) require an `X-Admin-Password` header matching `ADMIN_PASSWORD`. Returns `503` for every route if `ADMIN_PASSWORD` is unset.
|
|
|
|
```
|
|
POST /api/admin/auth/check -- { ok: boolean }
|
|
GET /api/admin/hosts -- every enabled NPM host, annotated with matchesPattern + any override
|
|
PUT /api/admin/hosts/:domain -- { visibility?, displayName?, checkPath?, sortOrder? }
|
|
DELETE /api/admin/hosts/:domain -- reset to default (delete the override row)
|
|
GET /api/admin/manual-targets
|
|
POST /api/admin/manual-targets -- { name, url, checkPath? }
|
|
PUT /api/admin/manual-targets/:id -- { name?, url?, checkPath?, enabled?, sortOrder? }
|
|
DELETE /api/admin/manual-targets/:id
|
|
```
|
|
|
|
`visibility` is one of `"default"` (follow `NPM_INCLUDE_PATTERNS`), `"hidden"`, or `"shown"`.
|