claude-logbook share server
Serves the sessions published with claude-logbook -s REF --share:
| Route | |
|---|---|
GET / |
Home with the instructions. |
GET /share/<id> |
The conversation as a page (the CLI's template.html). |
GET /share/<id>.txt |
Plain text transcript. |
GET /share/<id>.json |
The stored payload. |
POST /api/shares |
{"payload": {...}, "expire": "30d"} → 201 {id, url, secret, expires} |
PUT /api/shares/<id> |
Authorization: Bearer <secret>; replaces the payload. |
DELETE /api/shares/<id> |
Authorization: Bearer <secret>. |
GET /healthz |
ok. |
Anyone can create a share; only the holder of the secret returned on creation can replace or delete it. The server keeps only the secret's SHA-256.
Security model
- No HTML is accepted. The payload is the JSON of
claude-logbook --json -s; it is checked field by field, unknown keys are dropped, memories are refused, and the page is built from the template embedded in the binary. - The data goes inside
<script type="application/json">with<,>and&escaped, and the page is served with a CSP that allows only the template's two inline scripts by hash: no inline handlers, no external anything. - Share ids are 10 base62 characters from
crypto/rand; pages carryX-Robots-Tag: noindexandReferrer-Policy: no-referrer. - Images (
--images) are accepted only as base64data:URLs of PNG, JPEG, GIF or WebP whose bytes match the format they claim; SVG never. - Limits: request body (
MAX_BYTES), sessions per share (MAX_SESSIONS), and a token bucket per client IP for creations and for updates/deletes. X-Forwarded-Foris believed only when the connection comes fromTRUSTED_PROXY; otherwise the peer address counts.- Logs carry method, path, status and size, never bodies or headers.
Content is not encrypted: whoever runs the server can read the shares.
Configuration
| Env | Flag | Default |
|---|---|---|
ADDR |
-addr |
:8181 |
DATA_DIR |
-data |
/data |
PUBLIC_URL |
-public-url |
https://claude-logbook.all.ar |
MAX_BYTES |
-max-bytes |
20971520 (20 MB) |
MAX_SESSIONS |
-max-sessions |
1 |
TRUSTED_PROXY |
-trusted-proxy |
empty: trust no proxy (IPs or CIDRs, comma separated) |
CREATES_PER_HOUR |
-creates-per-hour |
10 per IP |
WRITES_PER_HOUR |
-writes-per-hour |
60 per IP |
Shares expire after 1d, 7d, 30d (default) or 90d. Expired ones return
404 at once and are deleted by a sweep every hour.
Storage is two files per share in DATA_DIR (<id>.json, <id>.meta), mode
600, written atomically. Back up that directory and you have backed up
everything.
Build and run
The image is built from the repository root, because it embeds
claude_logbook/template.html:
docker build -t claude-logbook-server .
docker run -d --name claude-logbook-server --restart unless-stopped \
-p 8181:8181 -v claude-logbook-data:/data \
-e TRUSTED_PROXY=203.0.113.10 \
claude-logbook-server
or with compose:
services:
claude-logbook:
build: .
image: claude-logbook-server
restart: unless-stopped
ports: ["8181:8181"] # 127.0.0.1:8181:8181 if NPM runs on this same host
volumes: ["claude-logbook-data:/data"]
environment:
TRUSTED_PROXY: 203.0.113.10 # the address NPM connects from
volumes:
claude-logbook-data:
The image is FROM scratch: one static binary, running as 65534:65534,
with a HEALTHCHECK that runs /server -healthcheck.
Nginx Proxy Manager
Add a proxy host claude-logbook.all.ar → <host>:8181, with an SSL
certificate (Let's Encrypt) and Force SSL. Add this under Advanced so
uploads up to MAX_BYTES get through:
client_max_body_size 20m;
TRUSTED_PROXY must be the address the connections from NPM arrive from.
With NPM on another machine, that is its public IP (203.0.113.10 above is
only an example), and the firewall should open 8181 to that IP only. If NPM
runs in Docker on the same host, it is its address on the Docker network
(e.g. 172.17.0.0/16) instead. If it is wrong, every visitor
seems to come from NPM, they all share one rate-limit bucket, and ten shares
an hour from anyone block everyone.
Development
cd server
go generate # copies ../claude_logbook/template.html (git-ignored)
go vet ./... && go test ./...
go run . -data /tmp/shares -public-url http://localhost:8181
CLAUDE_LOGBOOK_SERVER=http://localhost:8181 claude-logbook -s 3 --share