b39e4eac88
Three features turn out to be one mechanism: a command waiting to be approved, a question the model wants answered, and "this reply is waiting for you" are all — stop the generation, put an interactive block in the bubble, wait for a POST, carry on. So there is one primitive, and the only thing using it so far is `ask_user`: a model can offer you a few answers and a box to write your own. The shell executor is not here yet. This lands first on purpose, because it is the riskiest machinery in the feature and it is worth having working before any subprocess exists to complicate it. Two things about where the pause sits. It pauses a round, not a call: a round's calls run together under a semaphore, and parking four coroutines on four separate answers inside that gather would queue them behind each other invisibly. And Stop had to be taught about it — `cancel` is read between streamed chunks and there are no chunks while paused, so the button did nothing at all until `request_stop` learned to resolve the pause itself. Also here: a risk class on every tool (read, write, execute), which is what the four permission modes will be a table over, and the systemd unit loses ProtectKernelTunables. That last one is not tidying — it bind-mounts /proc/sys read-only, which stops bubblewrap mounting /proc at all, and the obvious workaround would expose this process's environment and with it the encryption key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
103 lines
4.0 KiB
Markdown
103 lines
4.0 KiB
Markdown
# Deployment
|
|
|
|
Installs LLeMbas as a **system** service behind nginx with a self-signed
|
|
certificate. Written for a systemd + nginx host; tested on Arch.
|
|
|
|
| | Default |
|
|
|---|---|
|
|
| Service user | `lembas` (system account, `nologin`) |
|
|
| Home | `/home/lembas` |
|
|
| Install prefix | `/srv/lembas` (bind mount of the home) |
|
|
| Checkout | `$PREFIX/app` |
|
|
| Virtualenv | `$PREFIX/venv` |
|
|
| Database | `$PREFIX/data/lembas.db` |
|
|
| Environment | `$PREFIX/lembas.env` (mode 600) |
|
|
| Unit | `/etc/systemd/system/lembas.service` |
|
|
| Vhost | `/etc/nginx/conf.d/<host>.conf` |
|
|
| Listens on | `127.0.0.1:8080` — reachable only through nginx |
|
|
|
|
The prefix defaults to a bind mount of the service user's home because on many
|
|
machines the root filesystem is small while `/home` is not, and the virtualenv
|
|
plus database belong on the larger volume. Set `PREFIX=$HOME_DIR` to skip it.
|
|
|
|
## First install
|
|
|
|
```bash
|
|
SITE_HOST=chat.example ./deploy/install.sh
|
|
```
|
|
|
|
Idempotent — safe to re-run. It creates the user and bind mount, clones the
|
|
repo, builds the venv, generates `lembas.env` with a fresh `LEMBAS_SECRET_KEY`,
|
|
installs the unit and vhost, issues a self-signed certificate, adds a
|
|
`/etc/hosts` entry if the name does not already resolve, and enables the
|
|
service.
|
|
|
|
Then open `https://<SITE_HOST>`, accept the certificate warning, and create the
|
|
first account — it becomes the administrator.
|
|
|
|
Everything is overridable from the environment:
|
|
|
|
| Variable | Default | |
|
|
|---|---|---|
|
|
| `SITE_HOST` | `lembas.local` | nginx `server_name` and certificate CN |
|
|
| `APP_PORT` | `8080` | loopback port the service binds |
|
|
| `SERVICE_USER` | `lembas` | system account to run as |
|
|
| `HOME_DIR` | `/home/lembas` | that account's home |
|
|
| `PREFIX` | `/srv/lembas` | install root (bind mount of `HOME_DIR`) |
|
|
| `REPO_URL` | this checkout's `origin` | so a fork deploys itself |
|
|
| `LEMBAS_BRANCH` | `main` | branch to deploy |
|
|
|
|
## Deploying a change
|
|
|
|
```bash
|
|
git push
|
|
./deploy/update.sh
|
|
```
|
|
|
|
`update.sh` fetches, hard-resets the deployment checkout to `origin/main`,
|
|
reinstalls dependencies and restarts, printing the commits it pulled. The hard
|
|
reset is deliberate: nothing is ever edited in place there, so there is no local
|
|
work to preserve and no conflicts to resolve.
|
|
|
|
## Operating it
|
|
|
|
```bash
|
|
systemctl status lembas
|
|
journalctl -u lembas -f
|
|
sudo -u lembas /srv/lembas/venv/bin/lembas info # paths and counts
|
|
```
|
|
|
|
Configuration lives in `$PREFIX/lembas.env`. Edit it and restart.
|
|
|
|
## Notes
|
|
|
|
**The secret key is generated once.** `install.sh` will not overwrite an
|
|
existing `lembas.env`. Rotating `LEMBAS_SECRET_KEY` signs every user out *and*
|
|
makes stored upstream API keys unreadable — they would have to be re-entered.
|
|
|
|
**nginx buffering is off for a reason.** Replies stream as server-sent events.
|
|
With `proxy_buffering on` (the default) nginx holds the entire reply and
|
|
delivers it in one lump at the end, which is indistinguishable from streaming
|
|
being broken. `proxy_read_timeout` is raised to an hour because a model can
|
|
think for minutes before the first token.
|
|
|
|
**Hardening is deliberately moderate.** `ProtectSystem=full`, not `strict`,
|
|
because agent chats run commands. Two lines in the unit are load-bearing and
|
|
worth knowing before anyone tidies them:
|
|
|
|
- **`ProtectKernelTunables` is absent on purpose.** With it, bubblewrap cannot
|
|
start at all — it bind-mounts `/proc/sys` read-only, and the kernel then
|
|
refuses `mount -t proc` inside a user namespace. The unit says why, and why
|
|
the obvious workaround is worse.
|
|
- **`TasksMax` and `MemoryMax` bound the whole service**, because a sandbox has
|
|
no cgroup of its own and `RLIMIT_NPROC` is counted per uid — the same uid the
|
|
server runs as.
|
|
|
|
Local agent execution is off until an administrator turns it on, and the
|
|
sandbox never binds the deployment prefix, so a command cannot read the
|
|
database or the encryption key.
|
|
|
|
**Use a real certificate if this is exposed beyond a trusted LAN.** The
|
|
self-signed cert exists so the install works with no external dependencies;
|
|
point `ssl_certificate` at a real one and nothing else needs to change.
|