# Technitium hub A small, dependency-free Node program that looks like ONE Technitium DNS Server to the console and answers from several real ones. It serves the console's static build and a read-only, Technitium-compatible `/api/*`: - `node=` forwards the call to that server, with that server's token. - No `node` (or `node=cluster`) asks every server in parallel and merges the answers (statistics are summed, top lists and query logs are merged). If a server does not answer, the others are shown and the response carries `hubWarnings`. - Anything that would change data is refused with `403`. The hub has its own sign-in. The servers themselves are not changed: each only needs a read-only API token. Create it for a user whose group has View, and nothing else, on Dashboard, Logs, Apps (the logging apps Query Logs reads from) and Settings (the blocking state). ## Configuration | Variable | Default | Meaning | |---|---|---| | `HUB_SERVERS` | required | Ordered `id=url` list, e.g. `dns=https://dns.example.net,ndns=https://ndns.example.net`. The first server is shown as the Primary node. | | `_TOKEN` | required | API token of each server; the id is uppercased with non-alphanumerics as `_` (`dns` gives `DNS_TOKEN`, `my-dns` gives `MY_DNS_TOKEN`). | | `HUB_USER`, `HUB_PASSWORD` | required | The hub's own sign-in. It refuses to start without them. | | `HUB_PORT` | `8080` | Listening port. | | `HUB_CACHE_SECONDS` | `15` | How long successful answers of the servers are cached. | | `HUB_TIMEOUT_MS` | `8000` | Per-request timeout towards a server. | | `HUB_DOMAIN` | `hub` | Shown as the server's domain in the console. | | `HUB_STATIC` | `./dist` | The console's build to serve. | ## Endpoints Public: `status`, `user/login`. After signing in: `user/session/get`, `user/logout`, `user/checkForUpdate` (always "no update") and these reads: `dashboard/stats/get`, `dashboard/stats/getTop`, `dashboard/metrics/json`, `apps/list`, `logs/query`, `logs/list`, `settings/get`, `admin/cluster/state` (synthesized). Everything else answers `403 The hub is read-only.` ## Run locally ```sh npm run build node dev/mock-api.mjs --port 5380 & node dev/mock-api.mjs --port 5381 --scale 2 & HUB_SERVERS=a=http://127.0.0.1:5380,b=http://127.0.0.1:5381 A_TOKEN=x B_TOKEN=y \ HUB_USER=admin HUB_PASSWORD=admin HUB_PORT=5390 npm run hub ``` Then open http://localhost:5390. Tests: `npx vitest run hub`. ## Docker ```sh docker run --rm -p 8080:8080 \ -e HUB_SERVERS=dns=https://dns.example.net,ndns=https://ndns.example.net \ -e DNS_TOKEN=... -e NDNS_TOKEN=... -e HUB_USER=admin -e HUB_PASSWORD=... \ git.shx.one/denozord/technitium-hub:latest ``` The image is built by `docker/Dockerfile.hub` and published by `.gitea/workflows/hub.yaml` (`latest` from `main`, `sha-<7>`, and `X.Y.Z` / `X.Y` from a `hub-vX.Y.Z` tag). A compose example with Traefik is in `docs/compose.hub.example.yaml`.