Fredrik Johansson 0711a7f7ad
All checks were successful
Docker / build-and-push (push) Successful in 1m51s
Add pull_policy: always, matching wisp/keep's convention
Missed when the admin-panel work copied wisp's docker-compose.yml
pattern. Without it, `docker compose up -d` won't re-pull a new
:latest image if a container with the old one is already running —
the host's bulk deploy script (docker compose up -d per project, no
explicit pull step) would silently leave this project stale on every
redeploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 20:46:32 +02:00
2026-07-09 19:55:08 +02:00

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

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

docker compose up -d

Uses the image from IMAGE in .env. For a local build:

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
{
  "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".

Description
No description provided
Readme MIT 102 KiB
Languages
TypeScript 97.6%
Dockerfile 2.4%