Files
LLeMbas/README.md
T
Jaroslav Beneš ba2fb1e13d Add registration toggle and password change; genericise deploy
Two things the running instance needed.

**Registration toggle.** Admin -> General, backed by a new settings table
group rather than the environment. LEMBAS_ALLOW_SIGNUP now seeds only the
initial value: once an administrator saves the setting, the stored value
wins. The alternative -- environment always winning -- means a toggle in
the UI silently reverts on the next restart, which is worse than not
offering one. Closing registration also removes the "Create one" link
from the sign-in page, so the link never leads somewhere that refuses.

**Password change**, on the user settings page. Changing a password
revokes every other session and immediately re-issues a cookie for the
current one: if the reason for the change is that somebody else knows
the password, leaving their session alive defeats the point, but signing
the user out of the tab they are standing in is merely rude.

**deploy/ is now host-agnostic.** This repository is public, so the unit
and vhost became templates with __PREFIX__ / __SITE_HOST__ / __APP_PORT__
substituted at install time, and every path, hostname and port moved to
environment variables. REPO_URL defaults to the checkout's own origin so
a fork deploys itself. Machine-specific values belong in private notes,
not here -- CLAUDE.md now says so.

83 tests, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 11:14:33 +02:00

6.3 KiB

LLeMbas — waybread for the long road of thought

A self-hosted web UI for your language models, written in Python.
Talks to anything that speaks the OpenAI API. Themed after Middle-earth.

Python 3.11+ License GPL-3.0 No Node required


Lembas is the Elvish waybread — one bite sustains a traveller for a day's march. The capitals hide what it runs on: LLeMbas.

Why this exists

Most self-hosted LLM front-ends are large JavaScript applications with a Python API bolted underneath. LLeMbas is the other way round: server-rendered Python, with htmx and a little Alpine for interactivity. There is no package.json, no bundler, no build step, and nothing is fetched from a CDN at runtime. Clone it, pip install -e ., run it.

Features

Working now

  • Chats — streaming replies, Markdown with server-side syntax highlighting, copy and regenerate, automatic chat titles
  • Folders — arbitrarily nested, delete a folder without losing the chats inside it
  • OpenAI connections — point at OpenAI, LM Studio, vLLM, llama.cpp, llama-swap, Ollama or OpenRouter; models are discovered and cached
  • Accounts — first account becomes the administrator, argon2 password hashing, revocable server-side sessions, self-service password change
  • Admin settings — open or close registration from the UI, stored in the database and effective immediately
  • Two themesMoria (dark) and Shire (light), switchable per user

Planned

Users & groups with permissions · file upload, vision and PDFs · built-in tools with admin settings · custom tools and MCP servers · agentic execution (local and over SSH) · image generation.

Quick start

git clone https://git.houmeres.sk/Houmeres/LLeMbas.git
cd LLeMbas

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"

cp .env.example .env
lembas secret-key           # paste the result into LEMBAS_SECRET_KEY

lembas serve                # http://127.0.0.1:8080

Open the address and create the first account — it becomes the administrator. Then go to Admin → Connections and add an endpoint. For a local runner that is usually http://localhost:1234/v1 with no API key. Press Test & refresh and its models appear in the chat model picker.

The vendored browser libraries (htmx, Alpine) are committed, so no network access is needed to run. To re-fetch or bump them: python scripts/fetch_vendor.py --update.

Configuration

All variables are prefixed LEMBAS_ and can live in .env. See .env.example for the annotated list.

Variable Default Purpose
LEMBAS_SECRET_KEY generated Signs sessions and encrypts stored API keys. Set this. A generated key changes every restart, signing everyone out and making stored API keys unreadable.
LEMBAS_DATA_DIR ./data SQLite database and uploads.
LEMBAS_HOST / LEMBAS_PORT 127.0.0.1 / 8080 Bind address.
LEMBAS_ALLOW_SIGNUP true Whether new users may register themselves — the initial value only. Once set under Admin → General the stored setting wins. The first account is always an admin regardless.
LEMBAS_DEFAULT_THEME moria moria (dark) or shire (light).
LEMBAS_SESSION_TTL 2592000 Session lifetime in seconds.
LEMBAS_REQUEST_TIMEOUT 300 Seconds to wait on an upstream model.

Commands

lembas serve          # run the server
lembas info           # where data lives, what is configured
lembas secret-key     # generate a value for LEMBAS_SECRET_KEY
lembas create-admin   # create or promote an administrator

How it fits together

Browser  ──form POST──▶  FastAPI  ──▶  SQLite
   ▲                        │
   │                        └──httpx──▶  any OpenAI-compatible endpoint
   └──── server-sent events ◀───────────────┘   (streamed reply)

Sending a message stores the turn and returns two HTML fragments: the user's bubble and an empty assistant bubble carrying an sse-connect. That opens a server-sent event stream which appends tokens as they arrive, then replaces the whole bubble with the finished, Markdown-rendered version. Rendering and highlighting happen in Python, so the streamed and final views cannot disagree.

src/lembas/
  api/         routes: auth, chats, folders, admin, pages
  db/models/   SQLAlchemy schema
  security/    password hashing, sessions
  services/    llm client, chat orchestration, markdown, crypto, sse
  web/         Jinja templates and static assets
assets/        SVG artwork masters
scripts/       artwork generator, vendored-JS fetcher
deploy/        systemd unit and nginx vhost for a real install

Development

pytest                              # test suite
ruff check .                        # lint
python scripts/build_artwork.py     # regenerate the SVG artwork
python scripts/fetch_vendor.py      # verify vendored JS against the lockfile

There is no migration tool. The schema is SQLite-only and created at startup, so changing a column on a live database is a manual job — see CLAUDE.md.

Artwork

The logo, favicon and banner are original vector work, generated by scripts/build_artwork.py so the mallorn leaf stays identical across every size it appears at. The wordmark is Source Serif 4 (SIL OFL 1.1) converted to outlines — a README banner cannot load a webfont, and <text> would render in whatever serif the reader happens to have.

Licence

GPL-3.0.

A note on the theme

This is an independent hobby project, themed as an affectionate nod to J.R.R. Tolkien's world. It is not affiliated with, endorsed by, or connected to the Tolkien Estate, Middle-earth Enterprises, or any related rights holder. All artwork here is original.