A KOReader sync service with decoupled runtime/data adapters, currently supporting:
- Cloudflare Worker + D1
- Local production (Node + SQLite)
Includes:
- KOReader-compatible sync APIs (register, auth, progress upload/fetch)
- Admin Web UI for user management (delete user, force reset password)
- User Web UI for personal login and statistics/records
Click the button below to deploy your own instance of KOReader Sync directly to Cloudflare Workers:
-
Configure Secrets: Once deployed successfully, go to your Cloudflare Worker dashboard -> Settings -> Variables. Add Secrets: Add
PASSWORD_PEPPERandADMIN_TOKENas encrypted secrets. -
Redeploy: Trigger a redeploy for the secrets to take effect.
-
Initialize Database: Visit your Worker URL at /admin, log in with your ADMIN_TOKEN, and click the Initialize Database button to create the required tables.
- Set production secrets:
npx wrangler secret put PASSWORD_PEPPER
npx wrangler secret put ADMIN_TOKEN- Apply remote migrations:
npx wrangler d1 migrations apply koreader-sync-db --remoteIf migrations are skipped, admins can initialize required tables from
/adminafter login.
- Deploy Worker:
npm run deployDocker now runs the Node local-production runtime (not wrangler dev):
- Build image:
docker build -t koreader-sync:prod-local .- Run container:
docker run --rm -p 8787:8787 \
-e RUNTIME_TARGET=node \
-e DB_DRIVER=sqlite \
-e SQLITE_PATH=/app/data/koreader-sync.db \
-e PASSWORD_PEPPER=your-strong-secret \
-e ADMIN_TOKEN=your-admin-token \
-v $(pwd)/data:/app/data \
koreader-sync:prod-local- Visit:
- Health check:
http://localhost:8787/healthcheck - User page:
http://localhost:8787/ - Admin page:
http://localhost:8787/admin
- Runtime targets:
cloudflare(implemented)node(implemented for local production)vercel(reserved interface, not implemented yet)
- Database drivers:
d1(Cloudflare runtime)sqlite(Node runtime)postgres(reserved interface, not implemented yet)
- Web UIs served directly by Worker:
/: user dashboard/admin: admin console
POST /users/createregister userGET /users/authauthenticate (x-auth-user+x-auth-key)PUT /syncs/progressupload progressGET /syncs/progress/:documentfetch progress by documentPUT /syncs/statisticssynchronize reading statistics snapshot
KOReader sends
x-auth-keyasmd5(plain_password). This service stores/verifies KOReader credentials against that value for protocol compatibility. MD5 is weak by itself; here it is only a protocol input and is still wrapped by server-side PBKDF2 hashing before storage.
POST /web/auth/loginlogin (sets HttpOnly cookie)POST /web/auth/logoutlogoutGET /web/mecurrent userGET /web/records?page=1&pageSize=20reading recordsGET /web/statsstatistics summaryGET /web/statistics/bookssynchronized books statistics listGET /user dashboard page
PUT /syncs/statistics request body:
{
"schema_version": 20221111,
"device": "KOReader Device Model",
"device_id": "device-id-or-empty-string",
"snapshot": {
"books": [
{
"md5": "partial-md5",
"title": "Book title",
"authors": "Author",
"notes": 0,
"last_open": 1710000000,
"highlights": 0,
"pages": 320,
"series": "Series #1",
"language": "en",
"total_read_time": 1234,
"total_read_pages": 88,
"page_stat_data": [
{
"page": 12,
"start_time": 1710000100,
"duration": 24,
"total_pages": 320
}
]
}
]
}
}Notes:
- Auth is identical to other KOReader sync endpoints (
x-auth-user,x-auth-key). - Server uses
md5as cross-device identity key for merge. - Response format:
{
"ok": true,
"snapshot": {
"books": []
}
}POST /admin/auth/loginadmin login with{ "token": "..." }POST /admin/auth/logoutadmin logoutGET /admin/mecurrent admin session statusGET /admin/init/statuscheck whether required tables existPOST /admin/initinitialize required database tables and indexesGET /admin/userslist usersDELETE /admin/users/:iddelete userPUT /admin/users/:id/passwordforce reset user passwordGET /adminadmin dashboard page
- Install dependencies:
npm install- Configure local vars:
cp .dev.vars.example .dev.vars
# Update PASSWORD_PEPPER with a strong random value
# Set ADMIN_TOKEN for admin console login- Create D1 database and update
wrangler.toml:
npx wrangler d1 create koreader-sync-db
# Put returned database_id into wrangler.toml- Apply migrations:
npx wrangler d1 migrations apply koreader-sync-db --localYou can also login to
/adminfirst and use the “Initialize database” button when required tables are missing.
- Start dev server:
npm run dev- Install dependencies:
npm install- Configure environment:
cp .env.example .env
# set PASSWORD_PEPPER and ADMIN_TOKEN
# then set:
# RUNTIME_TARGET=node
# DB_DRIVER=sqlite
# SQLITE_PATH=./data/koreader-sync.db- Run:
npm run start:local-prod- Endpoints:
- Health check:
http://localhost:8787/healthcheck - User page:
http://localhost:8787/ - Admin page:
http://localhost:8787/admin
- Password hashing uses PBKDF2-SHA256 with high iteration count
- Web session cookie:
HttpOnly + Secure + SameSite=Lax - Session token is stored hashed in database
- All SQL uses bound parameters
REQUIRED environment variables:
PASSWORD_PEPPER: required strong secret for password/session hashingADMIN_TOKEN: required for admin web loginSESSION_TTL_HOURS: optional session lifetime in hours, default168Runtime / database selector:RUNTIME_TARGET:cloudflare(default) ornode;vercelis reserved but not implementedDB_DRIVER:d1(default) orsqlite;postgresis reserved but not implementedSQLITE_PATH: sqlite db path forRUNTIME_TARGET=node+DB_DRIVER=sqliteOPTIONAL environment variables:DEBUG: optional ("1"/"true"enables debug error logs)PBKDF2_ITERATIONS: optional number of iterations for PBKDF2 hashing, default20000(adjust based on your performance/security needs)ENABLE_USER_REGISTRATION: optional ("1"/"true"to allow user self-registration, default is open registration)