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>
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
- On startup (and every 10 minutes), fetches all enabled proxy hosts from the NPM API
- Filters to domains matching
NPM_INCLUDE_PATTERNS— unless overridden per-host in the admin UI (see below) - HTTP-checks each host every 60 seconds
- 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_PATTERNSwould 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".