Find a file
Chmouel Boudjnah 0b35367200
Some checks are pending
build / binary (push) Waiting to run
build / container (push) Waiting to run
test / test (push) Waiting to run
test / lint-openapi (push) Waiting to run
fix: Store pairing codes as hash of MD5-derived key clients send
KOReader and Readest kosync clients never transmit the raw pairing
code: they compute `md5(password)` locally and send that digest as
both the registration password and the `x-auth-key` credential on
every subsequent request. The adapter was hashing the raw code for
storage and then hashing `md5(code)` for the device slot key, so
redemption could never match what a real client actually sent,
causing every pairing attempt to return 403.

- Introduced `KosyncUserKey` (MD5 derivation) and `KosyncPairingHash`
  (SHA-256 of the derived key) in the auth package so pairing codes
  are stored under the hash of what the client will actually present
- Removed the server-side `md5hex` helper that re-derived the key
  after registration; the received value is already the derived key
- Normalized the received key to lowercase before hashing to match
  canonical hex encoding
- Updated all pairing code creation sites (admin CLI, web UI,
  self-service) to use `KosyncPairingHash`
- Added `TestClientSendsMD5DerivedKeyNotTheCode` regression test
  verifying that the raw code is rejected and the derived key works
- Updated tests to send the derived key on the wire, matching real
  client behavior

Signed-off-by: Chmouel Boudjnah <chmouel@chmouel.com>
2026-08-29 09:54:01 +02:00
.github/workflows fix(deploy): name the environment variables the server reads 2026-08-18 22:29:57 +02:00
cmd/liseur-sync feat: Separate OPDS rate limiter from authentication limits 2026-08-28 23:08:28 +02:00
docs fix: Store pairing codes as hash of MD5-derived key clients send 2026-08-29 09:54:01 +02:00
internal fix: Store pairing codes as hash of MD5-derived key clients send 2026-08-29 09:54:01 +02:00
scripts test: keep screenshot fixture folders visible 2026-08-24 02:38:09 +02:00
.gitignore remove binary from root 2026-08-16 17:23:29 +02:00
.golangci.yml fix: add golangci-lint configuration and pre-commit hooks 2026-08-15 17:56:13 +02:00
.ko.yaml feat: Add web administration panel with account-level roles 2026-08-15 20:22:05 +02:00
.pre-commit-config.yaml fix: redirect after a settings mutation instead of rendering in place 2026-08-22 11:59:49 +02:00
.reflex.conf chore: Add development environment configuration and Makefile 2026-08-15 20:22:01 +02:00
AGENTS.md feat: scope catalog access to explicit per-user folder grants 2026-08-22 10:43:41 +02:00
CLAUDE.md feat: Implement initial reading position sync server backend 2026-08-10 16:19:12 +02:00
compose.yaml fix(deploy): name the environment variables the server reads 2026-08-18 22:29:57 +02:00
Dockerfile fix(deploy): name the environment variables the server reads 2026-08-18 22:29:57 +02:00
go.mod feat!: watch a folder, not a pipeline 2026-08-16 09:47:18 +02:00
go.sum feat!: watch a folder, not a pipeline 2026-08-16 09:47:18 +02:00
LICENSE Design document for liseur-sync 2026-08-10 13:06:47 +02:00
liseur-sync.example.toml fix: harden credential enrolment 2026-08-20 06:10:51 +02:00
Makefile chore: Update local development configuration template setup 2026-08-16 09:47:18 +02:00
README.md docs: replace em dashes and fix formatting 2026-08-22 07:33:05 +02:00
SECURITY.md fix: harden credential enrolment 2026-08-20 06:10:51 +02:00

liseur-sync

A self-hosted server for book and reading-progress sync.

Liseur's companion server. It also works with stock KOReader through a kosync-compatible API.

It is a single Go binary with SQLite by default, optional PostgreSQL, and multi-user support.

The dashboard

The reader-first library

Why liseur-sync

Komga and calibre-web both cover part of the job.

Komga has full Readium locator sync and a clean REST API. Its filesystem-first design means clients cannot upload or delete books; you put books in library folders by other means. Its JVM footprint is also large for a Raspberry Pi or small VPS (about 600 MB on disk).

calibre-web has a rich web UI and can delete books. Its Kobo-protocol sync carries percentages rather than exact reading positions. Upload and delete use browser forms behind sessions and CSRF tokens, which makes them a poor fit for another client.

Neither server syncs a book opened outside its catalog, such as one from a file manager. Neither offers personal series claims or an append-only sync log for deterministic conflict resolution.

liseur-sync combines those missing pieces: full-locator sync, including books resolved by hash; REST API upload and delete; personal series claims; and an append-only sync log. It stays small enough for a Raspberry Pi, at about 30 MB on disk.

Capability Komga calibre-web liseur-sync
Catalog and search REST API OPDS REST API + OPDS
File download REST API OPDS REST API
Position sync Full locators Percentages only Full locators
Book upload Not possible Web UI form only REST API
Book delete Not possible Web UI form only REST API
Series management Read-only None Personal claims
Footprint ~600 MB (JVM) ~200 MB (Python) ~30 MB (Go)

Features

Reading progress sync

liseur-sync records each user's reading positions from every device.

It keeps every update, not only the latest one. A stale device cannot overwrite newer progress, and the history drives reading sessions and statistics.

It matches books by content rather than filename, so a rename, move, or re-encoded EPUB can retain its reading position.

Watched folders and OPDS

Point liseur-sync at a directory of EPUBs or a Calibre library. It watches the folder, reads metadata, and serves books through the web UI, native catalog API, and OPDS 1.2.

Folders are read-only until an administrator enables uploads. Books stay where they are: the server does not modify, rename, or delete files below the folder root. An upload-enabled folder accepts a new file or Calibre entry from the library page or a client with the library-upload scope. The cover cache is the only other location where the server writes files.

liseur-sync admin folder-uploads <folder-id> on

Browse by series, contributor, or tag, and search within the selected folder. In a plain folder, subdirectories form the series structure. In a Calibre library, metadata.db is the catalog.

Book details and reading status

Web reader

The reader

liseur-sync includes a browser-based EPUB reader.

The browser unpacks and renders EPUBs without exposing publisher content through application routes. Scripts inside EPUBs do not run.

The reader syncs through the same API as other clients. Switch between it, Liseur, KOReader, and other apps without losing your place.

See ADR-0007 for the design and security model, including the optional separate hostname for the reader.

Quick start

The installer detects Docker or configures rootless Podman with a systemd user service. It then asks you to choose a database, starts the server, and creates the first account.

curl -fsSL https://raw.githubusercontent.com/chmouel/liseur-sync/main/scripts/install.sh | bash

If you prefer not to pipe a remote script directly into a shell, clone the repository and run:

scripts/install.sh

You can install a specific release with:

LISEUR_VERSION=vX.Y.Z scripts/install.sh

Docker Compose

Pick a database profile:

docker compose --profile sqlite up -d        # SQLite
docker compose --profile postgres up -d      # bundled PostgreSQL
docker compose --profile external up -d      # existing PostgreSQL server

Build from source

go build ./cmd/liseur-sync
./liseur-sync serve

Once the server is running, open /ui/.

When no accounts exist, the setup page opens automatically. The first account becomes the administrator.

Add a folder from Settings > Administration > Folders, or from a shell:

liseur-sync admin add-folder Shelf /srv/books

Use Settings or the admin CLI to manage users, folders, API tokens, and reader pairing.

The administration section

See docs/deployment.md for KOReader pairing, TLS, watched folders, Calibre integration, and backups.

Client integration

Liseur is the reference client.

If you are building another client, docs/integrating.md covers the synchronization model and protocol.

Full API spec: docs/openapi.yaml.

Security

See SECURITY.md for reporting security issues.

Please do not report security vulnerabilities through public GitHub issues.

License

MIT.

The screenshots use public-domain editions from Standard Ebooks and are generated by scripts/screenshots.sh.