tsundoku

積ん読

tsundoku

acquiring books and letting them pile up unread

An OPDS 1.2 and 2.0 catalog, an authenticated upload UI, and a KOReader-compatible progress sync server.

One Cloudflare Worker, one R2 bucket, one D1 database, inside the free tier.

What it does

Browse and download

OPDS 1.2 for KOReader, FBReader, Panels, Moon+ and PocketBook; OPDS 2.0 JSON for Thorium. Covers and metadata come along.

Sync reading position

KOReader's own kosync protocol, so the Progress sync plugin needs only the base address. Every device's position on every book, and whether they actually agree.

Upload through a web UI

EPUB and CBZ have their metadata and cover art read out of the file server-side. Files over 95 MB take the R2 multipart path automatically.

Screenshots

The library view: a grid of book covers with search, sorting and paging.
The library — covers, metadata and search, read out of the files on upload.
The sync view: reading positions grouped by book, each listing its devices with how far through each one is and how far behind the others are.
Sync — every device's position on every book, and whether they actually agree.
The administration view: storage used against the R2 free tier, the user list, and the form for adding a user.
Administration — storage against the free tier, and the closed user set.

Install

Needs Node 22+ and a Cloudflare account.

git clone https://github.com/pid1/tsundoku && cd tsundoku
npm install
npx wrangler login

./scripts/setup.sh      # bucket, database, migrations, secrets, admin password
npm run deploy

setup.sh is idempotent and prints the generated admin password at the end. Write it down. A custom domain is strongly recommended: some OPDS clients dislike workers.dev subdomains, and TLS is non-negotiable because authentication is HTTP Basic.

Connecting a reader

ClientWhereAddress
KOReader — catalog Search → OPDS catalog → + https://books.example.com/opds/1.2
KOReader — progress sync Tools → More tools → Progress sync https://books.example.com — the base address, no path
Thorium Reader Catalogs → Add catalog https://books.example.com/opds/2.0
FBReader, Moon+, PocketBook, Panels, Aldiko Add OPDS catalog https://books.example.com/opds/1.2

On KOReader, choose Login, not Register. Self-registration is disabled and answers 403. Set the same document matching method on every device: binary is the default and the right choice; filename breaks the moment you rename a file.

Free tier

LimitFree planWhat it means here
R2 storage10 GB-monthThe first ceiling. Roughly 2,000–5,000 EPUBs, or ~80 comics.
R2 egressFreeWhy this is on R2 and not S3.
Worker requests100,000 / dayEnormous for a household. The UI shell is static assets, which are unmetered.
Request body100 MBFiles over 95 MB upload via R2 multipart automatically.
D15 GB, 5M row reads/dayNowhere near it.

Figures read from Cloudflare's documentation on 2026-09-18. Re-check them before relying on them.

What it is not

PLAN.md has the design reasoning, the free-tier arithmetic and the open questions.