Skip to main content
Operator

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:

  1. Boots an embedded PostgreSQL on port 54330
  2. Auto-creates the botfather database
  3. Runs migrations
  4. 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
caution

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.

BindBOTFATHER_ADMIN_TOKEN/api/admin
127.0.0.1 (default)unsetopen — local-dev convenience
127.0.0.1setrequires the bearer token
0.0.0.0 / non-loopbackunsettower refuses to start
0.0.0.0 / non-loopbacksetrequires the bearer token
tip

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).

caution

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.

info

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

PortPurpose
8400Tower API + admin dashboard
54330Embedded PostgreSQL (when no external DB is configured)
5174UI 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