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