# 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 ``` `.env` is loaded via Node's native `--env-file` flag (both `dev` and `start` pass it) — no `dotenv` dependency. Docker doesn't use this at all; the container gets its config from `docker-compose.yml`'s `environment:` block instead, same as before. ## 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. ``` GET /api/latency-targets ``` ```json { "refreshedAt": "2026-07-26T18:00:00Z", "targets": [ { "key": "wisp.dev.example.com", "name": "Wisp", "url": "https://wisp.dev.example.com/", "source": "npm" } ] } ``` `/api/latency-targets` exposes the current computed check list after include-pattern filtering plus admin overrides/manual targets, making it suitable as a source of truth for downstream probe consumers. ### 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"`.