The easiest way to self-host this sync server is using Docker Compose:
- Create a new directory for your sync server:
mkdir koreader-sync && cd koreader-sync
- Create a
docker-compose.yml
file with the following content:
services:
kosync:
image: ghcr.io/nperez0111/koreader-sync:latest
container_name: kosync
ports:
- 3000:3000
healthcheck:
test: ["CMD", "wget" ,"--no-verbose", "--tries=1", "--spider", "http://localhost/health"]
interval: 5m
timeout: 3s
restart: unless-stopped
volumes:
- data:/app/data
- Start the server:
docker compose up -d
Your sync server will now be running at http://localhost:3000
. The SQLite database will be automatically persisted in a Docker volume.
- Open a document on your KOReader device and navigate to Settings → Progress Sync → Custom sync server
- Enter your server's URL (e.g.,
http://localhost:3000
if running locally) - Select "Register / Login" to create an account or sign in
- Test the connection by selecting "Push progress from this device now"
- Enable automatic progress syncing in the settings if desired
- Install dependencies:
bun install
- Set up environment variables:
# Create a .env file with the following variables (change values as needed):
PASSWORD_SALT="your_secure_random_string"
PORT=3000
HOST="0.0.0.0"
- Run the development server:
bun run dev
- POST
/users/create
- Headers:
content-type
: application/json
- Body:
{ "username": "string", "password": "string" }
- Response: 201 (Created) or 402 (Username exists)
- GET
/users/auth
- Headers:
x-auth-user
: Usernamex-auth-key
: Password/API Keyaccept
: application/vnd.koreader.v1+json
- Response: 200 (OK) or 401 (Unauthorized)
- PUT
/syncs/progress
- Headers:
x-auth-user
: Usernamex-auth-key
: Password/API Keyaccept
: application/vnd.koreader.v1+jsoncontent-type
: application/json
- Body:
{
"document": "8b03a82761fae0ee6cd5a23700361e74",
"progress": "/body/DocFragment[15]/body/div[65]/text()[1].41",
"percentage": 0.2082,
"device": "boox",
"device_id": "197E7C6B3FD54A749C87DE9C1B05A3CE"
}
- Response: 200 (OK) or 401 (Unauthorized)
- GET
/syncs/progress/:document
- Headers:
x-auth-user
: Usernamex-auth-key
: Password/API Keyaccept
: application/vnd.koreader.v1+json
- Response: 200 (OK) with progress data or 404 (Not Found)
- Password Hashing: All passwords are hashed using bcrypt with additional salt
- Custom Headers: Uses KOReader's custom authentication headers
- SQLite Security: Uses parameterized queries to prevent SQL injection
- Environment Variables: Sensitive configuration like password salt is loaded from environment variables
For production use, you should also:
- Use HTTPS to encrypt all traffic
- Implement rate limiting to prevent brute force attacks
- Add input validation and sanitization
- Regularly backup the SQLite database (located in
/app/data/koreader-sync.db
) - Consider adding request signing for additional security
- Use a strong, random PASSWORD_SALT in production