Mirror your KOReader reading progress from
sync.koreader.rocksinto Calibre-Web-Automated — so your CWA web UI shows where you actually are in each book, without installing any extra plugin on your KOReader devices.
KOReader's built-in Progress Sync plugin syncs reading position between devices via sync.koreader.rocks — the free public KOSync server run by the KOReader team. That works great for device-to-device sync, but the data lives in someone else's Redis and never reaches your Calibre-Web-Automated instance, so the CWA web UI has no idea what you're reading.
This bridge runs alongside CWA as a small Python sidecar. It periodically polls sync.koreader.rocks for each book in your CWA library and writes any new progress into CWA via its existing /kosync HTTP endpoint. Result: your CWA web UI starts reflecting current reading position, the "Read" checkmark flips when you finish a book, and CWA integrations (Hardcover, Kobo sync) get notified — all without installing any extra plugin on your e-readers.
- Not a replacement for
sync.koreader.rocks. Your devices keep using it as their primary sync server. If the bridge is down or you stop running it, device-to-device sync still works. - Not a KOSync server itself. It doesn't accept incoming sync requests. It's a one-way poll-and-write daemon.
- Not the
cwasyncplugin. That plugin (shipped by CWA) requires installing on every KOReader device. This bridge runs once on your server and works for all your devices automatically — and keeps your KOReader UI uncluttered.
KOReader devices ←──→ sync.koreader.rocks (unchanged primary sync)
▲
│ GET /syncs/progress/<hash>
│ (your kosync.rocks credentials)
│
kosync-cwa-bridge
(Python, Docker)
│
│ PUT /kosync/syncs/progress
│ (CWA credentials via HTTP Basic)
▼
CWA
The bridge knows which document hashes to poll for by reading CWA's metadata.db (read-only) — specifically the book_format_checksums table that CWA already maintains. So a book has to be in your CWA library to be tracked.
-
Get a kosync.rocks account. If your KOReader devices are already syncing progress, you already have one — same username and password you set up in KOReader's Progress Sync settings.
-
Get a CWA account. This is your normal CWA login. For better security, create an app password from the CWA profile page (
/me→ App Passwords) — it's revocable without changing your main password. See CWA PR #1269 for details. -
Compute the MD5 of your kosync.rocks password:
echo -n 'your_kosync_password' | md5sum
# PowerShell [System.BitConverter]::ToString( [System.Security.Cryptography.MD5]::Create().ComputeHash( [System.Text.Encoding]::UTF8.GetBytes('your_kosync_password')) ).Replace('-','').ToLower()
-
Create
config.yml(start fromconfig.example.yml):kosync_rocks: url: https://sync.koreader.rocks username: your_kosync_rocks_username password_md5: 5f4dcc3b5aa765d61d8327deb882cf99 cwa: url: http://calibre-web-automated:8083 username: admin password: ${CWA_PASSWORD} metadata_db_path: /calibre-library/metadata.db cache_db_path: /data/bridge_cache.db poll_interval_seconds: 300
-
Add to your Docker Compose (see
docker-compose.example.yml):services: kosync-cwa-bridge: image: ghcr.io/ericchansen/kosync-cwa-bridge:latest restart: unless-stopped environment: CWA_PASSWORD: ${CWA_PASSWORD} volumes: - ./config.yml:/config/config.yml:ro - /your/calibre-library:/calibre-library:ro - bridge-data:/data volumes: bridge-data:
-
Start it:
CWA_PASSWORD='your_cwa_password' docker compose up -d kosync-cwa-bridge docker compose logs -f kosync-cwa-bridgeYou should see something like:
INFO __main__: checking kosync.rocks auth... INFO __main__: checking CWA auth... INFO __main__: preflight ok INFO kosync_cwa_bridge.poller: forwarded checksum=8f13037c percentage=0.5125 device=tokay INFO kosync_cwa_bridge.poller: cycle complete: checked=341 hits=2 forwarded=2 cache_skips=0 errors=0 -
Watch CWA's web UI. Books you're reading on your devices should now show up with progress.
| Field | Required | Default | Description |
|---|---|---|---|
kosync_rocks.url |
yes | — | KOSync server URL (almost always https://sync.koreader.rocks) |
kosync_rocks.username |
yes | — | Your kosync.rocks username (same as KOReader Progress Sync) |
kosync_rocks.password_md5 |
yes | — | MD5 hex digest of your kosync.rocks password |
cwa.url |
yes | — | Base URL of your CWA instance (no trailing /kosync) |
cwa.username |
yes | — | CWA username |
cwa.password |
yes | — | CWA cleartext password (or app password — preferred) |
metadata_db_path |
yes | — | Path to CWA's Calibre metadata.db (in container) |
cache_db_path |
yes | — | Path for the bridge's local cache SQLite file |
poll_interval_seconds |
no | 300 |
How often to poll kosync.rocks for changes |
request_delay_seconds |
no | 0.2 |
Delay between per-book requests (be polite) |
log_level |
no | INFO |
DEBUG, INFO, WARNING, ERROR |
Any ${ENV_VAR} references in the YAML are substituted at load time. Missing env vars cause a fast-fail on startup.
# Long-running daemon (default)
python -m kosync_cwa_bridge --config /path/to/config.yml
# One cycle and exit (useful for testing or cron)
python -m kosync_cwa_bridge --config /path/to/config.yml --once
# Help
python -m kosync_cwa_bridge --helpThe KOSYNC_CWA_BRIDGE_CONFIG environment variable can replace --config. Inside the Docker image it defaults to /config/config.yml.
git clone https://github.com/ericchansen/kosync-cwa-bridge
cd kosync-cwa-bridge
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
# Run unit tests
.venv/bin/pytest -q
# Run lint
.venv/bin/ruff check src tests
# Run integration tests (requires live CWA + kosync.rocks creds via env vars)
KSR_USER=... KSR_PASS=... CWA_URL=... CWA_USER=... CWA_PASS=... \
CWA_METADATA_DB=... .venv/bin/pytest -m integrationThe bridge is a stateless poll loop:
while True:
for (book_id, format, checksum) in cwa.read_book_checksums():
progress = kosync_rocks.get_progress(checksum)
if progress and progress.timestamp > cache.get(checksum):
cwa.update_progress(progress)
cache.set(checksum, progress.timestamp)
sleep(poll_interval)Key points:
- Reads are direct SQLite reads against CWA's
metadata.dbopened with?mode=roURI — safe alongside a running CWA. - Writes to CWA go through its proper
/kosync/syncs/progressHTTP endpoint, so all CWA side-effects (Read status flip at ≥99%, Hardcover sync, Kobo state cascading updates) happen automatically. - The local cache is purely an optimization — delete
bridge_cache.dband the bridge will re-push everything on next cycle.
- Polling means there's a lag between reading something on your device and seeing it in CWA — by default 5 minutes. Lower
poll_interval_secondsif you want faster updates (but be polite to sync.koreader.rocks). - Only books in your CWA library are tracked. Sideloaded books on your devices that aren't in CWA won't appear in CWA's progress (which is correct — there's no library entry to attach progress to).
- Single user. v0.1 supports one kosync.rocks account ↔ one CWA account mapping. Multi-user is on the roadmap.
- One-way sync. Progress flows kosync.rocks → CWA. If you mark a book as Read in CWA's web UI, that change is not propagated back to KOReader.
MIT — see LICENSE
- KOReader — the e-reader app + the built-in kosync plugin
- koreader-sync-server — the KOSync protocol reference implementation
- Calibre-Web-Automated and Calibre-Web-NextGen — the library manager + the
/kosyncHTTP endpoint that makes this bridge trivial. Works with either fork.