Skip to content

Repository files navigation

shelfmark-eink

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.

Install

# 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-stopped
echo "SECRET_KEY=$(openssl rand -hex 32)" > .env
docker compose up -d

Then 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 --build

Auth — log in once, stay logged in

You 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.

E-readers: bookmark to stay signed in

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.

Security — read this before exposing it

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 it false for 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.

Config

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.

Screenshots

Screenshot 2026-08-27 at 9 36 46 AM

Caveats

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.

License

MIT

About

A no-JavaScript, server-rendered front-end for Shelfmark that loads on Kobo and Kindle e-ink browsers

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages