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>
Establish the LLeMbas foundation: FastAPI/Jinja/SQLite layout, the ORM
schema, and the original SVG identity.
Notable decisions, all recorded in comments at the point they matter:
- No Alembic. SQLite only, schema created at startup, so models carry a
few columns nothing reads yet (Message.parent_id for branching,
content_parts_json for multimodal turns). Adding them later to a live
database without migrations is the painful path.
- Sessions are server-side rows keyed by a SHA-256 of the cookie value,
not JWTs, so logout and bans revoke access immediately.
- Upstream API keys are Fernet-encrypted with a key derived from
LEMBAS_SECRET_KEY. decrypt() fails soft to "" so rotating the secret
degrades to re-entering keys rather than crashing the admin UI.
- Artwork is generated by scripts/build_artwork.py rather than hand-drawn
per file: the mallorn leaf appears in the icon, favicon, lockup and
banner, and one source is the only way those stay in sync. The wordmark
is Source Serif 4 (OFL) converted to outlines, because a README banner
cannot load a webfont and <text> would render in whatever serif the
viewer happens to have.
- Icons live in a template partial, not assets/, because same-document
<use href="#id"> is universally supported and the cross-document form
is not.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>