Self-Hosting
Botfather is the self-hosted control tower for your SLAW fleet. Your organisation deploys it once; every SLAW instance reports to it. This page covers getting the tower running.
Zero-config first run
Botfather boots with no external dependencies. On first start it:
- Boots an embedded PostgreSQL on port
54330 - Auto-creates the
botfatherdatabase - Runs migrations
- Listens on port
8400(the API and admin dashboard)
In development, the UI runs on port 5174 and proxies /api to :8400.
pnpm install
pnpm dev
Open the dashboard and you'll land on the Fleet view — empty until you enroll your first instance.
External PostgreSQL
For production, point Botfather at your own PostgreSQL instead of the embedded one:
BOTFATHER_DATABASE_URL=postgres://user:pass@host:5432/botfather pnpm start
When BOTFATHER_DATABASE_URL is set, the embedded Postgres is not started. Run migrations against your database before first boot.
Securing the tower
The tower is built to fail closed: a fresh install is reachable only on the local machine, and you must opt in — with authentication — before it can be reached over a network.
Loopback bind by default
The tower binds to 127.0.0.1 (loopback) out of the box, so nothing off the host can reach it. The recommended posture is to front the tower with a reverse proxy that terminates TLS and forwards to 127.0.0.1:8400.
To bind on another interface, set BOTFATHER_BIND:
export BOTFATHER_BIND=0.0.0.0 # widen the bind deliberately
Widening the bind exposes the admin API. The tower refuses to start when bound to a non-loopback interface unless an admin token is configured — this prevents an unauthenticated admin API from ever being reachable off-box.
Admin authentication
The /api/admin surface (fleet view, approvals, budgets, skill publishing) is guarded by a shared admin secret. Generate a strong token and export it:
export BOTFATHER_ADMIN_TOKEN="$(openssl rand -hex 32)"
The admin UI and API authenticate with an Authorization: Bearer header carrying this token, compared in constant time. Store it in your secret manager and the UI's login session — never in source or localStorage.
| Bind | BOTFATHER_ADMIN_TOKEN | /api/admin |
|---|---|---|
127.0.0.1 (default) | unset | open — local-dev convenience |
127.0.0.1 | set | requires the bearer token |
0.0.0.0 / non-loopback | unset | tower refuses to start |
0.0.0.0 / non-loopback | set | requires the bearer token |
In production, run with BOTFATHER_ADMIN_TOKEN set even on loopback, so the gate is active end to end and rotating the secret is a single change.
Enrollment trust
By default enrollment is token-less: anyone who can reach the tower can create a pending enrollment, and an admin gates admission. To stop the network from even seeding the pending queue, set a pre-shared enrollment secret:
export BOTFATHER_ENROLLMENT_SECRET="$(openssl rand -hex 32)"
When set, POST /enroll must present the matching secret — as the enrollmentSecret request field or the x-botfather-enrollment-secret header — compared in constant time. Mismatches are rejected with 401 enrollment_secret_required before any row is written. Distribute the secret to instances via the same channel that points them at the tower URL (on the SLAW side, SLAW_BOTFATHER_ENROLLMENT_SECRET).
Auto-approve rules are convenience, not a trust boundary — machineId and hostname are self-asserted and spoofable. Wildcard patterns (*, *.*, …) are refused at creation. Use a specific pattern, or leave enrollments pending for manual approval. Only auto-approve on a trusted enrollment network, or alongside the pre-shared secret above.
TLS
The tower speaks plain HTTP and expects a TLS-terminating proxy in front of it. Because it binds to loopback, plaintext never crosses a network boundary. Configure HSTS at the proxy and forward to http://127.0.0.1:8400.
The shared-secret admin gate is the pre-SSO v1. The auth middleware is a single function, so single sign-on (e.g. EntraID) can be added later without changing any route wiring.
Ports at a glance
| Port | Purpose |
|---|---|
8400 | Tower API + admin dashboard |
54330 | Embedded PostgreSQL (when no external DB is configured) |
5174 | UI dev server (proxies /api → :8400) |
Background jobs
Once running, the tower runs several background jobs:
- A status sweeper that marks instances offline or stale
- Hourly rollups for cost analytics
- An alert evaluator for budget, offline, and spend-spike alerts
- A daily retention job (see Retention)
Next steps
- Enroll an Instance — connect your first SLAW instance
- Enforcement Modes — enforce vs advisory
- Retention — data retention windows
- Security & Sovereignty — the full security posture