Files
LLeMbas/deploy
Jaroslav Beneš 191394fa08 Deploy the ssh extra, from one place
update.sh installed `[search]` only, so the release that added agent
connections shipped without asyncssh and the feature offered an install
hint on a machine that had just been told to install it.

The extras are now one variable, spelled the same way in install.sh and
update.sh, with a comment in both saying they have to stay in step. That is
the whole failure mode: an extra added to one of them is an extra existing
deployments silently miss.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 22:36:48 +02:00
..
2026-08-01 22:36:48 +02:00

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

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

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

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.

Nothing an agent does runs on this machine. Agent chats execute their commands over SSH, on a host somebody added and prepared — a container, a VM, another machine. That is the whole isolation story, and it is why the unit can stay locked down instead of being opened up to make room for a sandbox.

ProtectSystem=full rather than strict only because the data directory must be writable and strict would mean listing every path.

The practical consequence for whoever runs this: the security of an agent chat is the security of the host behind its SSH profile. A throwaway container with the one project mounted into it is a very different thing from a key to a production server, and LLeMbas cannot tell them apart.

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.