mlua-swarm 0.29.0

Swarm engine host built on mlua — long-running stateful runtime with Role/Verb gate, CapToken, 3-stage pipeline, and Middleware overlay.
Documentation
# Running `mse serve` on Fly.io

Single-machine reference deployment for remote self-hosting (GH #101
Layer 3). The security model behind every step here — what the access
token is, what `token_secret` is, why the server refuses to start
half-configured — is `mse://guides/auth-token-model`.

What you get: one always-on machine running `mse serve` behind Fly's
TLS edge, state on a volume, the whole `/v1` surface behind the L0
access token. TLS terminates at the edge; the server speaks plain HTTP
inside, and clients reach it as `https://` (the operator WS client maps
that to `wss://` on its own).

The whole flow below is scripted — for the common path just run:

```bash
MSE_FLY_APP=<your-app-name> bash contrib/fly/deploy.sh
```

The script is idempotent (existing app / volume / secrets / IPs are
reused) and ends with the verification ladder from §3. The sections
below explain what it does.

## Prerequisites

- `flyctl` authenticated (`fly auth login`)
- this directory (`contrib/fly/`) as your working directory

## 1. Create the app and volume

```bash
fly launch --no-deploy --copy-config   # accepts fly.toml, asks for the app name
fly volumes create mse_data --size 1   # same region as primary_region
```

Edit `fly.toml`: set `app`, `primary_region`, and (optionally) pin a
newer image tag.

## 2. Set the two secrets

```bash
fly secrets set \
  MSE_ACCESS_TOKEN="$(openssl rand -hex 32)" \
  MSE_TOKEN_SECRET="$(openssl rand -hex 32)"
```

- `MSE_ACCESS_TOKEN` — the L0 perimeter token. Mandatory: binding
  `0.0.0.0` without it, the server exits at startup with a clear error
  (fail-closed), so forgetting this step breaks the deploy instead of
  exposing an open server.
- `MSE_TOKEN_SECRET` — the CapToken signing key (hex). Optional but
  strongly recommended: unpinned, it is regenerated every boot and a
  machine restart invalidates all outstanding worker tokens (the server
  warns about this on non-loopback binds).

Keep the access-token value — every client needs it.

## 3. Deploy and verify

Two operational notes learned on the first live deploy:

- **Public IPs**: the automatic allocation on first deploy has been
  observed to fail (`error allocating ipv6`), leaving the app deployed
  but unreachable (empty `fly ips list`, curl `000`). Fix:
  `fly ips allocate-v4 --shared` (+ `allocate-v6`). `deploy.sh` does
  this pre-emptively.
- **Building from source** (e.g. verifying unreleased changes with
  `fly deploy --dockerfile Dockerfile` from the repo root) relies on the
  repo's `.dockerignore` — without it the build context uploads the full
  checkout including a multi-GB `target/`.

```bash
fly deploy
APP=https://<your-app>.fly.dev
curl -s -o /dev/null -w '%{http_code}\n' $APP/v1/healthz    # 200 (exempt)
curl -s -o /dev/null -w '%{http_code}\n' $APP/v1/status     # 401 (gate works)
curl -s -o /dev/null -w '%{http_code}\n' \
  -H "X-MSE-Access-Token: <token>" $APP/v1/status           # 200
```

Restart survival check: `fly machine restart <id>`, then repeat the
authorized `/v1/status` call — state (Blueprints, runs, operator
sessions) persists on the volume, and outstanding CapTokens stay valid
because `token_secret` is pinned.

## 4. Point clients at it

```bash
export MSE_HTTP=https://<your-app>.fly.dev
export MSE_ACCESS_TOKEN=<token>
```

With those two set, the whole client surface works unchanged: the
operator WS client connects through the TLS edge (`wss://`), and worker
fetch/submit and `mse bp build --register --server ...` attach the
`X-MSE-Access-Token` header automatically. For an MCP client (e.g.
Claude Code), put both variables in the `mse` server's `env` block.

## Constraints (by design)

- **One machine, one volume.** SQLite single-writer + in-process engine
  state — do not add machines or regions to this app.
- **Autostop stays off** (`auto_stop_machines = "off"` in fly.toml):
  the operator WS is long-lived and Fly does not guarantee active
  WebSockets survive autostop.
- **Browser WebSocket clients are unsupported** (they cannot send the
  custom access-token header); clients are the native `mse` tooling.
- The access token is a single shared static secret — rotation is
  `fly secrets set MSE_ACCESS_TOKEN=<new>` (triggers a restart) plus
  updating every client. Per-operator credentials are out of scope
  (see the guide's non-goals).