Semora
A self-hosted pastebin for HTML and Markdown with a back-channel to the agent that wrote it.
Any agent with a shell publishes a file or a folder with one command and gets a URL. You open it in your browser, read it with your favourite theme, and answer from the page: comments anchored to text, drawings sent as screenshots, messages, and events the page itself sends. The agent receives them with semora wait, fixes the document, pushes again, and your open tab reloads.
One Rust server binary (semora-server) and one Rust CLI (semora). SQLite plus a content-addressed blob folder. No Node, no CDN: every browser library is embedded. The gallery shows a thumbnail of every document, drawn by the server itself (no headless browser) in a separate, time-limited process.
Run the server
Docker
Binary + systemd
Then follow the comments in deploy/semora.service; put deploy/Caddyfile (or any reverse proxy) in front for TLS. Websockets pass through.
First run
Open the public URL. Every page redirects to /setup until you create the admin (your email and a password) with the setup token printed in the server log (only someone with console access can finish setup). Then optionally add a passkey or an authenticator app, and choose the site title and whether sign-up is open.
Configuration
Environment variables or semora.toml (see crates/semora-server/semora.toml.example):
| Variable | Default | Meaning |
|---|---|---|
SEMORA_PUBLIC_URL |
http://localhost:<bind port> |
Public address (no path) |
SEMORA_BIND |
127.0.0.1:8080 |
Listen address |
SEMORA_DATA_DIR |
data |
Database, blobs and secret.key |
SEMORA_RP_ID |
public host | WebAuthn relying-party id |
SEMORA_MAX_UPLOAD_MB |
25 |
Largest bundle |
SEMORA_MAX_FILES |
2000 |
Most files in a bundle |
SEMORA_KEEP_REVISIONS |
20 |
Revisions kept per document |
SEMORA_TRUST_PROXY |
false |
Take client IPs from the last X-Forwarded-For entry (one reverse proxy) |
SEMORA_CONTENT_CONNECT_SRC |
'self' |
connect-src for document pages |
SEMORA_THUMBNAIL_RENDERER |
this binary | Program that draws gallery thumbnails, or off |
SEMORA_LOG |
info |
Log filter |
Use HTTPS in production: the session cookie is Secure and passkeys need a secure context (http://localhost counts).
Backup: copy the data folder (stop the server, or use SQLite's online backup for semora.db).
Back up semora.db before you upgrade. A new version may change the database, and an older one will not open it again.
Themes: eleven Markdown themes are built in (GitHub auto/light/dark, Air, Modest, Retro, Splendor, Sakura, Tufte, Newsprint, Night). Drop your-theme.css into the data folder's themes/ directory and restart to add one; a file named like a built-in theme replaces its stylesheet. Rendered Markdown sits in <article class="markdown-body" id="write">.
Semora sends email for sign-up, invites, password resets and email changes. It works without a mail server: sign-up and "forgot password" are hidden, and the admin page gives you invite and reset links to copy and send yourself. "Create an invite link" on the admin page makes a link that is not tied to an email address: it works once, for 7 days, and whoever opens it gives their own address when they sign up.
| Variable | Default | Meaning |
|---|---|---|
SEMORA_SMTP_HOST |
unset (mail off) | SMTP server |
SEMORA_SMTP_PORT |
587, or 465 with tls |
SMTP port |
SEMORA_SMTP_TLS |
starttls |
starttls, tls (TLS from the start) or none (only for a relay on the same machine) |
SEMORA_SMTP_USER |
unset | SMTP user name |
SEMORA_SMTP_PASSWORD |
unset | SMTP password; environment only, never in semora.toml |
SEMORA_MAIL_FROM |
required with a host | Sender, e.g. Semora <semora@example.com> |
Links in emails use SEMORA_PUBLIC_URL, so set it. The admin page's Mail card sends you a test email.
Use it
Install the CLI with cargo install semora-cli (or cargo install --path crates/semora-cli from a checkout), then:
Inside a git checkout, make it a project so every worktree and machine updates the same documents:
On another machine or worktree with the committed .semora, semora pull copies the project's documents into the folder, and semora status lists what changed here and what is newer on the server. A push of a file you pulled stops (exit code 5) if someone pushed it since, so no edit is lost without notice.
The owner can arrange a project's tree with rules, from the project page's Arrange section or with semora project map; links and pushes always use the real path.
The full guide for people and agents is served at /docs (and /docs.md), and printed by semora skill, which you can paste into any agent's instructions. Source: crates/semora-proto/guide.md.
After an update, each person sees what changed since they last looked, in one pop-up on their next page. The Changelog page, linked from the gallery, has every release.
A project's owner can invite people at three levels. A Viewer reads, a Reviewer also sends comments, drawings and messages, and an Editor also publishes and receives feedback with semora wait. Manage collaborators on the project page. An admin can also let project owners invite people who have no account yet (the switch is on the admin page and is off by default).
Security model
- Document bytes are served under
/c/<signed-token>/…into an iframe withsandboxand noallow-same-origin, and every/c/response repeats the sandbox in its CSP. Uploaded HTML runs at an opaque origin: it cannot read Semora's cookies, storage or pages. - The session cookie is
__Host-,HttpOnly,Secure,SameSite=Strict. Cookie-authenticated API calls and websocket upgrades must come from Semora's own origin; forms and writes carry CSRF tokens. - Content URLs carry an HMAC token (document, revision, user, one-hour expiry); the viewer mints a fresh one on every navigation.
- Passwords use Argon2id; session tokens, recovery codes and emailed links are stored hashed. API keys are stored hashed (that is what is checked) and also encrypted, so you can copy your own keys from the account page; TOTP secrets are encrypted the same way, with a key from the data folder.
- Every request is checked against the caller's level in the project, on every route, socket and long poll. People outside a project get "not found". Removing someone takes effect at once, including pages they have open and agents that are waiting.
- Every response is
noindex, androbots.txtdisallows everything.
Develop
Crates: semora-proto (shared types), semora-server (Axum, SQLite, templates, embedded assets), semora-cli (the CLI, which installs the semora command). The server compiles in CHANGELOG.md (through the link crates/semora-server/CHANGELOG.md): a line that ends in <!-- whats-new --> also goes in the pop-up. Vendored browser libraries and themes are listed in docs/VENDOR.md. The design lives in docs/spec/semora-design-spec.md.
License
MIT. Vendored libraries keep their own licenses (see crates/semora-server/assets/vendor/LICENSES.txt and themes/LICENSES.txt).