Skip to content

rcarmo/bun-opds-server

Repository files navigation

bun-opds-server

A lightweight read-only OPDS server that merges multiple Calibre libraries into a single catalog, with EPUB, PDF, CBZ, and CBR downloads for devices like the XteInk X4 running CrossPoint Reader.

A small read-only OPDS server for multiple Calibre libraries under one filesystem tree.

It scans for metadata.db files, merges supported book formats across libraries, deduplicates by title, and exposes simple OPDS feeds for ebook readers.

It is intended to work especially well with CrossPoint Reader on the XteInk X4 and similar eink devices that can consume OPDS catalogs and direct downloads.

Current MVP

  • recursively discovers Calibre libraries under CALIBRE_ROOT
  • reads metadata.db in read-only mode
  • merges all books with available EPUB, PDF, CBZ, and/or CBR files
  • deduplicates entries by normalized book title, keeping the newest copy
  • exposes OPDS feeds for:
    • recent additions (capped for ereader compatibility)
    • recently updated (capped for ereader compatibility)
    • per-library views
    • search results (capped for ereader compatibility)
  • exposes lightweight HTML browse pages with:
    • constrained cover display
    • minimal metadata (series, tags, updated/published dates, short description)
    • direct EPUB/PDF/CBZ/CBR download links when available
  • supports pagination for browse/feed views
  • keeps HTML browse/search views unbounded by the feed cap so full libraries remain browseable
  • supports scored search ordering across title, authors, series, tags, and library
  • serves direct EPUB/PDF/CBZ/CBR downloads when present
  • optionally serves cover images
  • supports optional HTTP basic auth
  • includes a minimal KOReader progress sync API with SQLite-backed state

Why this exists

This is aimed at setups where:

  • there are multiple separate Calibre libraries
  • they live under one parent tree
  • a reader should be able to point at one OPDS endpoint
  • the main use case is: show me the most recent books and let me download them in whatever format is available

Endpoints

JSON / service

  • /health
  • /libraries

OPDS

  • /opds
  • /opds/recent
  • /opds/updated
  • /opds/library/:librarySlug
  • /opds/search?q=dune

HTML browse views

  • /
  • /browse/recent
  • /browse/updated
  • /library/:librarySlug
  • /search?q=dune

Asset / download

  • /download/:librarySlug/:bookId/epub
  • /download/:librarySlug/:bookId/pdf
  • /download/:librarySlug/:bookId/cbz
  • /download/:librarySlug/:bookId/cbr
  • /cover/:librarySlug/:bookId

KOReader sync

  • POST /users/create
  • GET /users/auth
  • PUT /syncs/progress
  • GET /syncs/progress/:document

Search results are ranked primarily by title matches, then by author/series/tag/library matches, with recency as a tiebreaker. OPDS feeds stay capped to the configured feed limit; HTML browse and search views can paginate through the full result set.

Quick start

cd /workspace/projects/bun-opds-server
cp .env.example .env
bun run index.ts

Or with inline variables:

cd /workspace/projects/bun-opds-server
CALIBRE_ROOT=/volume1/books \
BASE_URL=http://localhost:8787 \
bun run index.ts

Environment

Variable Default Purpose
CALIBRE_ROOT /volume1/books Root directory to scan for Calibre libraries
HOST 0.0.0.0 Bind host
PORT 8787 Bind port
BASE_URL http://localhost:$PORT Public base URL used in OPDS links
FEED_LIMIT 100 Max entries in recent/updated feeds
REFRESH_MS 600000 Background refresh interval
BASIC_AUTH_USER unset Optional basic auth username
BASIC_AUTH_PASS unset Optional basic auth password
KOSYNC_DB_PATH $CALIBRE_ROOT/koreader.db SQLite path for KOReader sync state

Development

bun run index.ts --help
bun run --watch index.ts

Docker image

Tagged releases publish a container image to:

  • ghcr.io/rcarmo/bun-opds-server

Run with Docker

docker run --rm -p 8787:8787 \
  -e CALIBRE_ROOT=/books \
  -e BASE_URL=http://localhost:8787 \
  -v /path/to/books:/books:ro \
  ghcr.io/rcarmo/bun-opds-server:latest

Run with Docker Compose

A sample Compose file is included as:

  • docker-compose.yml

It supports PUID / PGID via Compose user mapping so the container can read host-mounted libraries as the expected user and group.

Example .env values:

PUID=1000
PGID=1000
docker compose up -d

Releases

A GitHub Actions workflow builds and publishes the container image on every pushed tag matching v*.

For semver tags like v0.1.0, the workflow publishes tags such as:

  • ghcr.io/rcarmo/bun-opds-server:0.1.0
  • ghcr.io/rcarmo/bun-opds-server:0.1
  • ghcr.io/rcarmo/bun-opds-server:0
  • ghcr.io/rcarmo/bun-opds-server:latest

Example:

git tag v0.2.3
git push origin v0.2.3

KOReader sync

This project now includes a minimal compatible implementation of KOReader's progress sync API.

What it does

  • stores reading progress in a SQLite database
  • defaults to keeping that database at the top of the Calibre tree as koreader.db
  • auto-creates users on first successful auth/sync interaction
  • stores only per-user document progress state, not book files or filenames

Auth model

KOReader sends auth headers:

  • x-auth-user
  • x-auth-key

The sync endpoints use those directly. If a user does not already exist, the first successful request creates it. Later requests must present the same key.

Stored progress model

Per synced document, the server stores:

  • document (32-character MD5 document identifier from KOReader)
  • progress
  • percentage
  • device
  • device_id
  • update timestamp

Scope

This is intentionally limited to progress sync. It does not attempt to extend OPDS itself or add broader account management.

KOReader device setup example

On the KOReader device, set the custom sync server URL to your server base URL, for example:

  • https://books.example.net
  • or http://192.168.1.50:8787 on a trusted LAN

The KOReader sync plugin will talk to these endpoints on that same base URL:

  • GET /users/auth
  • PUT /syncs/progress
  • GET /syncs/progress/:document

Example self-hosted setup flow:

  1. Open Progress sync settings in KOReader.
  2. Enter the custom server base URL, e.g. http://192.168.1.50:8787.
  3. Choose a username.
  4. Choose a password/key.
  5. Let KOReader authenticate or sync once.

Because this server auto-creates users on first successful use, there is no separate admin-side provisioning step.

Design notes

  • intentionally read-only
  • currently assumes cover images live at cover.jpg inside each Calibre book directory
  • cover display in HTML views is size-constrained with CSS; server-side physical resizing is still a future improvement
  • currently deduplicates by normalized title and keeps the newest matching item
  • search uses simple weighted scoring across title, authors, series, tags, and library name
  • currently exposes a minimal OPDS 1.x-style feed aimed at ebook readers, with EPUB/PDF/CBZ/CBR acquisition links when present

Next likely steps

  • better cover detection
  • server-side thumbnail generation / resizing
  • deployment examples (Compose / reverse proxy)
  • additional OPDS compatibility testing across readers and eink devices

About

Read-only multi-library OPDS server for Calibre, built for the XteInk X4 and CrossPoint Reader.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors