Skip to content

A simple KOReader progress server for syncing reading progress across multiple devices

License

Notifications You must be signed in to change notification settings

Henri93/koreader-progress-server-python

Repository files navigation

Simple KOReader Progress Sync Server

Tests License: GPL v3

A simple Python/FastAPI server for syncing reading progress across KOReader devices.

Quick Start (Hosted Server)

Don't want to deploy your own? Use the public sync server:

https://www.null-space.xyz/reader

Just point your KOReader or Readest app to this URL and create an account.

Warning

Kindle Sync Timeout Issue

If you can login from KOReader on Kindle but sync fails with "something went wrong when syncing progress", you may need to increase KOReader's sync timeout. This commonly happens when using serverless backends (like AWS Lambda) that have cold start delays. If more people use this service, I can make it more available to improve response times.

Symptoms:

  • Login works, but push/pull progress fails/isn't working
  • KOReader debug log shows: KOSyncClient:update_progress failure: common/Spore/Protocols.lua:85: wantread

Fix:

  1. Connect your Kindle to your computer via USB
  2. Open the file: <Kindle>/koreader/plugins/kosync.koplugin/KOSyncClient.lua
  3. Find this line near the top (around line 6):
    local PROGRESS_TIMEOUTS = { 2,  5 }
  4. Change it to:
    local PROGRESS_TIMEOUTS = { 5, 15 }
  5. Save the file, eject Kindle, and restart KOReader

This increases the sync timeout from 5 seconds to 15 seconds, giving the server enough time to respond during cold starts.

Running Sync Server (Self-Hosted)

Local Development

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reload --port 8080

Docker

docker compose up -d

AWS Lambda

Deploy as a serverless Lambda function with DynamoDB storage.

Prerequisites

  • AWS CLI configured with credentials
  • Terraform >= 1.0
  • Python 3.12

One-Time Bootstrap

Create the shared S3 bucket for Terraform state:

cd deployment
./bootstrap.sh

Deploy

  1. Create terraform variables file:
cp deployment/terraform.tfvars.example terraform/terraform.tfvars
  1. Edit terraform/terraform.tfvars with your password salt:
password_salt = "your-secure-random-salt"  # Generate with: openssl rand -hex 32
  1. Build and deploy:
./deployment/deploy.sh

This creates:

  • Lambda function (reader-progress-prod)
  • DynamoDB tables (reader-progress-prod-users, reader-progress-prod-progress)
  • IAM role with minimal permissions

Configuration

Docker / Local Development

Environment Variable Default Description
DB_BACKEND sql Database backend (sql or dynamodb)
PASSWORD_SALT default-salt-change-me Salt prepended to passwords before hashing
DATABASE_URL sqlite:///./data/koreader.db SQLite/PostgreSQL database URL

AWS Lambda

Environment Variable Description
DB_BACKEND Set to dynamodb
PASSWORD_SALT Salt for password hashing (set via Terraform)
DYNAMODB_USERS_TABLE Users table name (set via Terraform)
DYNAMODB_PROGRESS_TABLE Progress table name (set via Terraform)
AWS_REGION AWS region (set via Terraform)

KOReader Setup

  1. Open a book in KOReader
  2. Open the menu (swipe down from top) and tap the settings icon (wrench)
  3. Select Progress sync > Custom sync server > enter your server URL (e.g., http://your-server:8080)
  4. Select Register or Login and enter your credentials
  5. Select Push progress from this device to test
  6. optional Enable Auto sync for automatic progress updates when opening/closing books

iOS Setup

Since, iOS doesn't seem to have a KOReader app I could find, I went with Readest which supports KOReader progress sync.

  1. Open Readest
  2. Open a book(I used Calibre content server's OPDS library to sync book files across devices)
  3. Open book menu table of contents in the bottom left(bullet list icon)
  4. Select hamburger menu in the top right > Select KOReader Sync
  5. Enter enter your server URL (e.g., http://your-server:8080) and credentials

How It Works

Multi-User Model

Each user has their own isolated account with separate reading progress data. Users authenticate via HTTP headers on every request, and progress is stored per-user, per-document.

User A (kindle)     User A (phone)      User B (tablet)
     │                   │                    │
     └───────┬───────────┘                    │
             │                                │
      User A's Progress                User B's Progress
      ┌─────────────┐                  ┌─────────────┐
      │ book1: 25%  │                  │ book1: 80%  │
      │ book2: 50%  │                  │ book3: 10%  │
      └─────────────┘                  └─────────────┘

Progress Sync Flow

  1. Device reads a book - KOReader calculates a document hash (MD5 of the file) and tracks reading position
  2. Device uploads progress - Sends document hash, XPath position, percentage, and device info
  3. Another device opens same book - Queries server using the same document hash
  4. Server returns latest progress - Device can jump to the synced position

The document hash ensures the same book is identified across devices regardless of filename.

Database Structure

SQLite/PostgreSQL (Docker/Local)

Users Table

Column Type Description
id INTEGER Primary key
username TEXT Unique username
password_hash TEXT Bcrypt hash of salted MD5 password

Progress Table

Column Type Description
id INTEGER Primary key
user_id INTEGER Foreign key to users.id
document TEXT MD5 hash of the document
progress TEXT XPath position (e.g., /body/p[42])
percentage REAL Reading progress 0.0-1.0
device TEXT Device name (e.g., "Kindle Paperwhite")
device_id TEXT Unique device identifier
timestamp INTEGER Unix timestamp of last update

Key constraint: One progress record per (user_id, document) pair. Updates replace existing records.

DynamoDB (AWS Lambda)

Users Table

Attribute Type Key
username String Partition Key
password_hash String -

Progress Table

Attribute Type Key
user_id String Partition Key
document String Sort Key
progress String -
percentage Number -
device String -
device_id String -
timestamp Number -

API Reference

Authentication

All endpoints except /users/create and /health require authentication via headers:

x-auth-user: <username>
x-auth-key: <md5_hash_of_password>

Password Format

KOReader sends passwords as MD5 hashes in all requests (both registration and authentication). The server then applies additional salting and bcrypt hashing before storage.

Password flow:

  1. Client computes MD5(raw_password)
  2. Client sends MD5 hash to server (in JSON body for registration, in x-auth-key header for auth)
  3. Server stores bcrypt(salt + md5_hash) in database

When using curl or other clients, you must send the MD5 hash of the password, not the raw password:

# Generate MD5 hash of password
echo -n "mypass" | md5  # macOS
echo -n "mypass" | md5sum | cut -d' ' -f1  # Linux

Endpoints

POST /users/create

Register a new user account.

Note: KOReader sends the password as an MD5 hash during registration. When using curl or other clients, you must send the MD5 hash of your password, not the raw password.

# MD5 of "mypass" is a029d0df84eb5549c641e04a9ef389e5
curl -X POST http://localhost:8080/users/create \
  -H "Content-Type: application/json" \
  -d '{"username": "myuser", "password": "a029d0df84eb5549c641e04a9ef389e5"}'

Response: {"status": "success"} (201) or {"detail": "Username already exists"} (402)

GET /users/auth

Verify credentials are valid.

# MD5 of "mypass" is a029d0df84eb5549c641e04a9ef389e5
curl http://localhost:8080/users/auth \
  -H "x-auth-user: myuser" \
  -H "x-auth-key: a029d0df84eb5549c641e04a9ef389e5"

Response: {"status": "authenticated"} (200) or {"detail": "Unauthorized"} (401)

PUT /syncs/progress

Update reading progress for a document.

curl -X PUT http://localhost:8080/syncs/progress \
  -H "Content-Type: application/json" \
  -H "x-auth-user: myuser" \
  -H "x-auth-key: a029d0df84eb5549c641e04a9ef389e5" \
  -d '{
    "document": "0b229176d4e8db7f6d2b5a4952368d7a",
    "progress": "/body/DocFragment[42]/body/p[3]/text().0",
    "percentage": 0.25,
    "device": "Kindle Paperwhite",
    "device_id": "A1B2C3D4"
  }'

Response: {"status": "success"} (200)

GET /syncs/progress/{document}

Retrieve the latest progress for a document.

curl http://localhost:8080/syncs/progress/0b229176d4e8db7f6d2b5a4952368d7a \
  -H "x-auth-user: myuser" \
  -H "x-auth-key: a029d0df84eb5549c641e04a9ef389e5"

Response:

{
  "document": "0b229176d4e8db7f6d2b5a4952368d7a",
  "progress": "/body/DocFragment[42]/body/p[3]/text().0",
  "percentage": 0.25,
  "device": "Kindle Paperwhite",
  "device_id": "A1B2C3D4",
  "timestamp": 1706123456
}

GET /health, GET /healthcheck

Health check endpoints for monitoring.

References

  • calibre - calibre is a powerful and easy to use e-book manager. It’s also completely free and open source and great for both casual users and computer experts.

  • koreader/koreader - KOReader ebook reader application

  • koreader-calibre-plugin - A calibre plugin to synchronize metadata from KOReader to calibre.

  • readest - Readest is a modern, open-source ebook reader for immersive reading. Seamlessly sync your progress, notes, highlights, and library across macOS, Windows, Linux, Android, iOS, and the Web.

Other Sync server implementations:

About

A simple KOReader progress server for syncing reading progress across multiple devices

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •