A tiny server-rendered front-end for Shelfmark, built so it loads in the stripped-down WebKit browsers on e-ink readers (Kobo, Kindle) where Shelfmark's normal JavaScript UI renders a blank page.
It talks to Shelfmark's JSON API and serves plain HTML <form>s — full-page
reloads, no client-side JavaScript, large high-contrast type. The same class of
browser that renders Calibre-Web fine renders this fine.
Kobo browser ──HTML──> shelfmark-eink ──JSON API──> shelfmark:8084
Search, pick a release, queue the download, watch the queue — the workflow mirrors the real UI. Covers are proxied through the shim, because the e-reader usually cannot reach Shelfmark directly.
# docker-compose.yml
services:
shelfmark-eink:
image: ghcr.io/zachtidwell/shelfmark-eink:latest
container_name: shelfmark-eink
environment:
SHELFMARK_URL: http://shelfmark:8084
SECRET_KEY: ${SECRET_KEY:?openssl rand -hex 32}
ports:
- 8090:5000
restart: unless-stoppedecho "SECRET_KEY=$(openssl rand -hex 32)" > .env
docker compose up -dThen browse to http://<your-server>:8090 from the e-reader.
If Shelfmark runs in Docker too, the shim needs to share a network with it —
see the note at the bottom of the shipped docker-compose.yml.
Keep SECRET_KEY stable: it signs the session cookie and derives the key that
encrypts your stored credentials, so changing it logs every device out.
To build from source instead of pulling the image:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --buildYou log in with the same account Shelfmark uses (this was built against
AUTH_METHOD: cwa, i.e. Calibre-Web credentials). Your credentials are
encrypted into the session cookie with a key derived from SECRET_KEY, which
never leaves the server. That means:
- The reader stays logged in across browser close and sleep.
- The shim is stateless, so it survives its own restarts without forcing a re-login — it decrypts the cookie and re-authenticates behind the scenes.
- Shelfmark rotates its signing key on restart; when that invalidates the upstream session, the shim transparently logs in again on the next request.
The Kobo's browser discards cookies when you close it, so cookies alone
cannot keep you logged in there. After logging in, bookmark the page — or
tap ★ Stay signed in in the nav and bookmark where it lands. That URL
carries an encrypted ?key=… token, so every visit re-authenticates with no
login screen even after the cookie is gone.
The ?key= token is a bearer credential, and it rides in a URL. It is your
password, encrypted, and the server decrypts it to log in to Shelfmark on your
behalf. URLs leak in ways cookies do not: browser history, Referer headers,
proxy and server access logs, screenshots, someone reading over your shoulder.
The token has no expiry and no revocation. Anyone holding that URL has your
Shelfmark access until you rotate SECRET_KEY (which logs out every device) or
change your password.
That tradeoff is deliberate — it is the only way to stay logged in on a browser that throws away cookies — but it means:
- Keep this on your LAN or behind a VPN. Do not put it on the open internet.
- Do not share the bookmark URL.
- If you do serve it over HTTPS, set
COOKIE_SECURE=true. Leave itfalsefor plain-HTTP LAN access or the browser will never send the cookie back.
The cover proxy refuses to fetch from loopback, link-local, RFC1918 and any
subnet this container is attached to, so a logged-in user cannot turn it into a
window onto neighbouring services. If private hosts are reachable through a
gateway rather than a directly-attached link, list them in COVER_DENY_CIDRS.
| Env | Default | Purpose |
|---|---|---|
SHELFMARK_URL |
http://shelfmark:8084 |
Shelfmark API base |
SECRET_KEY |
(required) | Signs the cookie + derives the credentials key |
COOKIE_SECURE |
false |
Set true when served over HTTPS |
SHOW_COVERS |
true |
Show small cover thumbnails |
COVER_DENY_CIDRS |
(empty) | Extra CIDRs the cover proxy must refuse |
REQUEST_TIMEOUT |
30 |
Per-request timeout to Shelfmark, in seconds |
Security reports go through GitHub's private vulnerability reporting — see SECURITY.md, which also lists the design tradeoffs that are known and intentional.
This is an independent client, not affiliated with Shelfmark. It calls
Shelfmark's internal JSON endpoints (/api/metadata/search, /api/releases,
/api/status), which carry no compatibility promise — an upstream refactor can
break it without warning. Issues and PRs welcome.
MIT