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​