A self-hosted EPUB library with cross-device reading sync. Read on your e-reader (KOReader, Moon+ Reader), pick up where you left off in the browser -- and vice versa. Built with Laravel + React, installable as an offline-capable PWA.
- EPUB Library Management: Upload, organize, and manage your EPUB collection
- Integrated EPUB Reader: Full-featured browser-based reader with customizable fonts (Literata, EB Garamond, Crimson Pro, Atkinson Hyperlegible...), themes, and layout settings
- PWA Support: Install as a standalone app, read offline with cached books
- Automatic Metadata Extraction: Title, author, description, cover image extracted from EPUB files
- Reading Progress Tracking: Track reading progress and total reading time per book
- Reading Sessions: Detailed history of reading sessions with duration and progress
- Cross-Device Reading Sync: Read on your e-reader, pick up where you left off in the browser -- and vice versa (see Multi-Device Sync below)
- OPDS Catalog: Browse and download books from any OPDS-compatible reader
- Multi-user Support: Each user has an isolated library
- Admin Dashboard: Manage users and view statistics
- Dark Theme UI: Modern, responsive interface with Tailwind CSS
The simplest way to run Varbook. A single container with everything included.
Varbook supports two database backends:
| Mode | Command | Best for |
|---|---|---|
| SQLite (default) | docker compose up -d |
Single user, simple setup, low resource usage |
| MySQL/MariaDB | docker compose -f docker-compose.yml -f docker-compose.mysql.yml up -d |
Multiple concurrent users, larger libraries |
Note: Varbook has been extensively tested and is used in production with MySQL/MariaDB. SQLite support is newer and works well for typical usage, but if you encounter any issue specific to SQLite, please open an issue on GitHub.
git clone https://github.com/ndieschburg/varbook.git
cd varbook
docker compose up -dThat's it. Varbook is running at http://localhost:8080 with SQLite -- no external database needed.
Create your admin account:
docker exec -it varbook php artisan varbook:create-admingit clone https://github.com/ndieschburg/varbook.git
cd varbook
# Set database passwords in .env.docker.local
cat > .env.docker.local <<EOF
DB_PASSWORD=your_secure_password
DB_ROOT_PASSWORD=your_root_password
EOF
docker compose -f docker-compose.yml -f docker-compose.mysql.yml up -dCreate your admin account:
docker exec -it varbook php artisan varbook:create-adminThere are two config files, each with a different purpose:
| File | Read by | Purpose |
|---|---|---|
.env |
Docker Compose | Host-level settings (port mapping) |
.env.docker.local |
Container (app) | Application settings (URL, DB passwords, etc.) |
Changing the port (e.g. if 8080 is already in use):
# .env (create at project root)
VARBOOK_PORT=3002Changing application settings (URL, limits, etc.):
Default values are in .env.docker. To override, create .env.docker.local:
# .env.docker.local
APP_URL=http://localhost:3002
BOOKSHELF_MAX_UPLOAD_SIZE_MB=100Note:
SANCTUM_STATEFUL_DOMAINSis automatically derived fromAPP_URL— you don't need to set it manually. Just make sureAPP_URLmatches how you access Varbook in your browser (including the port).
Example — running on port 3002:
# .env
VARBOOK_PORT=3002
# .env.docker.local
APP_URL=http://localhost:3002| Volume | Path in container | Content |
|---|---|---|
varbook_storage |
/var/www/html/storage/app |
EPUBs, covers, WebDAV locks |
varbook_logs |
/var/www/html/storage/logs |
Application logs |
varbook_database |
/var/www/html/database |
SQLite database (SQLite mode only) |
varbook_db |
/var/lib/mysql |
MariaDB data (MySQL mode only) |
# View logs
docker compose logs -f varbook
# Run artisan commands
docker exec -it varbook php artisan <command>
# Backup SQLite database
docker cp varbook:/var/www/html/database/database.sqlite ./backup.sqlite
# Rebuild after update
git pull
docker compose build
docker compose up -dIf you run Varbook behind a reverse proxy, make sure to set APP_URL and SANCTUM_STATEFUL_DOMAINS in your .env and forward the proper headers:
server {
listen 443 ssl;
server_name books.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 100M;
}
}Install without Docker on a bare server
- PHP 8.2+
- MySQL 8.0+ / MariaDB 10.6+ (or SQLite)
- Composer
- Node.js 18+ (for building assets)
- PHP Extensions:
mbstring,xml,dom,gd,zip,pdo_mysqlorpdo_sqlite
git clone https://github.com/ndieschburg/varbook.git
cd varbookcomposer install --no-dev --optimize-autoloader
npm ci && npm run buildcp .env.example .env
php artisan key:generateEdit .env with your database credentials and application URL:
APP_NAME=Varbook
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=varbook
DB_USERNAME=your_db_user
DB_PASSWORD=your_db_password
SESSION_DRIVER=database
# Sanctum SPA domains (for cookie auth)
SANCTUM_STATEFUL_DOMAINS=your-domain.com
# Varbook Configuration
BOOKSHELF_MAX_SESSION_HOURS=4
BOOKSHELF_SESSION_GAP_MINUTES=10
BOOKSHELF_FINISHED_THRESHOLD=95
BOOKSHELF_MAX_UPLOAD_SIZE_MB=50php artisan migrate --forcechmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cachephp artisan varbook:create-adminphp artisan config:cache
php artisan route:cache
php artisan view:cacheAccess the application at your configured URL. The React SPA provides:
- Library: Browse, search, filter, and upload books
- Book Detail: View metadata, reading sessions, download or delete
- Reader: Integrated EPUB reader with progress sync
- Stats: Reading statistics, charts, and session history
- Profile: Update name, email, and password
- Admin: User management (admin only)
Varbook can be installed as a Progressive Web App:
- Open the site in Chrome/Edge/Safari
- Click "Install" in the address bar or browser menu
- The app will be available as a standalone application
- Books can be downloaded for offline reading
When reading offline, your reading positions are stored locally and automatically synced when you come back online:
- Positions are saved to IndexedDB while offline
- On reconnection, all queued positions are batch-synced to the server
- Local positions take priority to prevent overwriting recent offline reading with old server data
Debug Mode: Enable "Debug mode" in Settings > EPUB Reader to see detailed logs for offline sync operations ([NetworkState], [OfflineDB], [PositionSync], [OfflineSync]) in the browser console.
- Use the search bar to find books by title or author
- Filter by status: All, Reading, Finished, Not Started
- Sort by: Recent, Title, Author, Progress
Each book's detail page shows a history of reading sessions:
- Date and time of the session
- Duration (e.g., "45 min")
- Progress change (e.g., "32% → 45%")
- Client used (Web Reader, Moon+ Reader, KOReader, etc.)
Varbook's main feature is seamless reading position sync between the web reader and e-reader apps. Read a chapter on your Kobo before bed, open the web app on your phone the next morning, and you're right where you left off.
Each client tracks reading position in its own native format (EPUB CFI for the web reader, XPointer for KOReader, percentage for others). Varbook bridges them using a pivot format that stores the spine item index and the position within that chapter. This allows accurate cross-device navigation even though each reader engine paginates differently.
| Scenario | Navigation method |
|---|---|
| Web → Web (same device) | EPUB CFI (exact position) |
| KOReader → KOReader | XPointer (exact position) |
| KOReader → Web | Pivot (spine index + chapter percentage) |
| Web → KOReader | Pivot (spine index + chapter percentage) |
| Any client → fallback | Global percentage |
Varbook ships with a dedicated KOReader plugin (varbook.koplugin) that replaces the built-in kosync progress sync with richer cross-device support.
Via the KOReader App Store (recommended):
- Install the App Store plugin if you don't have it yet
- Open KOReader → Search → Plugin store
- Search for Varbook Sync and tap Install
Manual installation: see the plugin README for instructions.
- Open a book in KOReader
- Go to Tools → Varbook
- Configure Server URL (
https://your-domain.com) - Set up your API Token using one of two methods:
- Pairing code (recommended): On the web app Profile page, click "Pair a device", enter a device name, and note the 5-digit code. On KOReader, tap Pair with code and enter the code — the token is transferred automatically. The code expires after 2 minutes.
- Manual entry: Generate a token from your Profile page, then copy and paste it into the API Token field on KOReader.
- Tap Sync now to push/pull reading positions
The plugin:
- Tracks every page turn locally in SQLite (works offline)
- On manual sync: pushes all unsynced positions to the server, or navigates to the server position if another device has read further
- Uses binary search on CREngine's page/fragment mapping for accurate pivot resolution
- Can be assigned to any gesture via Tools → Gestures → Varbook Sync
Moon+ Reader sync via WebDAV is experimental and not extensively tested. It works for basic position sync but may have edge cases.
Moon+ Reader Pro supports both OPDS (for browsing/downloading books) and WebDAV (for syncing reading positions).
- Open Moon+ Reader Pro
- Go to Menu → Net Library → OPDS catalogs
- Tap + to add a new catalog
- Enter:
- Name: Varbook
- URL:
https://your-domain.com/opds - Username: Your Varbook email
- Password: Your Varbook password
- Go to Menu → Miscellaneous → Sync reading positions
- Select WebDAV as the sync method
- Enter:
- WebDAV URL:
https://your-domain.com/webdav - Username: Your Varbook email
- Password: Your Varbook password
- WebDAV URL:
- Tap Test to verify the connection
Any OPDS-compatible reader can browse and download books from Varbook:
| Endpoint | Description |
|---|---|
/opds |
Root catalog |
/opds/all |
All books (paginated) |
/opds/by-author |
Browse by author |
/opds/by-author/{author} |
Books by specific author |
/opds/search?q={query} |
Search by title or author |
| Protocol | Auth Method | Used By |
|---|---|---|
| Web SPA | Sanctum (cookie) | Browser |
| Varbook API | Bearer token | KOReader plugin |
| OPDS | HTTP Basic Auth | Moon+ Reader, etc. |
| WebDAV | HTTP Basic Auth | Moon+ Reader |
| kosync | Header auth (x-auth-user, x-auth-key) |
KOReader (legacy) |
All endpoints require Sanctum authentication (cookie-based for SPA).
POST /api/login # Login, returns user
POST /api/logout # Logout
GET /api/user # Current user
PUT /api/user/locale # Update locale preference
PUT /api/user/profile-information # Update name/email
PUT /api/user/password # Change password
GET /api/stats # Reading statistics
GET /api/books # List books (with filters)
POST /api/books # Upload new book (multipart)
GET /api/books/{id} # Get book details
DELETE /api/books/{id} # Delete book
GET /api/books/{id}/download # Download EPUB
GET /api/books/{id}/cover # Get cover image
GET /api/books/{id}/sessions # Reading sessions
GET /api/books/{id}/progress # Get progress
PUT /api/books/{id}/progress # Update progress
POST /api/books/{id}/progress/batch# Batch sync (PWA offline)
# API Tokens
GET /api/tokens # List tokens
POST /api/tokens # Create token
DELETE /api/tokens/{id} # Revoke token
POST /api/tokens/pairing-code # Generate 5-digit pairing code
# Pairing (public, no auth)
POST /api/pairing/claim # Claim code, receive token
# Admin only
GET /api/admin/users # List users
POST /api/admin/users # Create user
PUT /api/admin/users/{id} # Update user
DELETE /api/admin/users/{id} # Delete user
GET /api/admin/stats # Global statistics
# Create a new admin user
php artisan varbook:create-admin
# Clear application cache
php artisan optimize:clear
# Rebuild cache
php artisan config:cache
php artisan route:cache
php artisan view:cache| Variable | Default | Description |
|---|---|---|
BOOKSHELF_MAX_SESSION_HOURS |
4 | Maximum duration for a single reading session |
BOOKSHELF_SESSION_GAP_MINUTES |
10 | Maximum gap between syncs to stay in the same session |
BOOKSHELF_FINISHED_THRESHOLD |
95 | Progress percentage to mark a book as finished |
BOOKSHELF_MAX_UPLOAD_SIZE_MB |
50 | Maximum EPUB file size for upload |
- Framework: Laravel 12 (PHP 8.2+)
- Database: SQLite (default) or MySQL/MariaDB
- Auth: Laravel Sanctum (SPA), HTTP Basic (OPDS/WebDAV)
- WebDAV: sabre/dav
- EPUB Parsing: kiwilan/php-ebook
- Framework: React 18 + TypeScript
- Routing: React Router 7
- Data Fetching: TanStack Query
- Styling: Tailwind CSS 4
- Build Tool: Vite 7
- i18n: react-i18next
- EPUB Reader: epub.js
- Offline Storage: Dexie (IndexedDB)
- PWA: vite-plugin-pwa + Workbox
A Calibre plugin is available for uploading books directly from Calibre desktop.
Location: calibre_plugin_varbook/
Build with:
cd calibre_plugin_varbook
./build.shThis project is open-sourced software licensed under the MIT license.
