Self-hosted book management platform β automatically search, download, and manage your ebook library from your Hardcover reading lists.
- Full Book Pipeline: Hardcover sync β Prowlarr search β qBittorrent download β EPUB import with metadata β E-reader sync
- Library Browser: Browse your book collection with cover images, filtering, and sorting
- Library Import: Scan an existing books folder and match files to your library β auto-matching by embedded ID, ISBN, and title/author, with a review queue for fuzzy and unmatched files
- Search Page: Search for books via Prowlarr indexers and grab results for download
- Download Queue: Real-time download progress tracking via qBittorrent
- Book Detail & Metadata Editor: Edit title, author, series, and other metadata per book
- EPUB Metadata Writing: Automatically writes title, author, and series info into EPUB files
- Folder Organization: Configurable library structure β flat, by author, by series, or by author/series
- Pipeline Automation: Configurable status β action mapping (e.g., "want to read" triggers search + download + E-reader sync)
- Multi-E-reader Support: Configure multiple E-reader devices and choose which to sync to
- Automatic E-reader Sync: One toggle in Settings runs a full shelf sync hourly, every 6 hours, or daily
- RSS Sync (Auto-Grab): Automatically monitor Prowlarr indexers for new releases matching your wanted books
- Real-time Progress: Live WebSocket updates during operations
- Web Interface: Modern Vue 3 dashboard for configuration, scheduling, and monitoring
- Docker Support: Run in a container with easy deployment
- Comprehensive logging and error handling
- Dry-run mode for testing
- Docker (recommended) or Python 3.11+
- Hardcover account with API token
- Prowlarr instance for book search
- qBittorrent instance for downloads
- E-reader with SSH access (jailbroken) connected via Tailscale
Your E-reader must expose an SSH server to receive book transfers. Any device that can run one works. On a Kindle, that typically involves:
- Jailbreaking the device - enables running custom software
- Installing KUAL (Kindle Unified Application Launcher) - app launcher for custom apps
- Installing USBNetwork or similar - enables SSH access
- Setting up Tailscale (recommended) - secure network access without port forwarding
For detailed jailbreak instructions, see the MobileRead Wiki; other e-readers (e.g. Kobo) have equivalent guides there.
Note: BookOtter uses SSH/SFTP to transfer files directly to your E-reader's filesystem. The E-reader appears as a standard Linux host with root access.
git clone https://github.com/Boren/BookOtter.git
cd BookOttermkdir -p data
cp config.yaml.example data/config.yaml
# Edit data/config.yaml with your settingsdocker-compose up -dOpen http://localhost:6887 in your browser.
version: "3.8"
services:
bookotter:
build: .
container_name: bookotter
ports:
- "6887:6887"
volumes:
# Persistent data (config, database, logs)
- ./data:/app/data
# Book library (organized EPUBs)
- ./library:/app/library
# SSH keys for E-reader access
- ~/.ssh:/root/.ssh:ro
environment:
- TZ=Europe/Oslo
restart: unless-stopped| Mount | Purpose |
|---|---|
./data:/app/data |
Config file, SQLite database, and logs |
./library:/app/library |
Book library β organized EPUBs managed by BookOtter |
~/.ssh:/root/.ssh:ro |
SSH keys for E-reader access |
The web UI provides:
- Browse your book collection with cover images
- Filter by status, author, series
- Sort by title, date added, author
- View book details and edit metadata
- Search for books via Prowlarr indexers
- View search results with size, seeders, indexer info
- Grab results to start downloading via qBittorrent
- View active download queue with real-time progress
- Cancel downloads
- Live speed and ETA from qBittorrent
- Trigger Hardcover sync and E-reader sync manually
- View real-time progress with WebSocket updates
- See library statistics
- Configure Hardcover API token
- Configure Prowlarr and qBittorrent connections
- Manage multiple E-reader devices
- Manage root folders for library organization
- Test all connections
- Configure pipeline automation
- Enable automatic E-reader sync (hourly, every 6 hours, or daily; offline E-readers are skipped until the next run)
- View application logs in real-time
- Filter by log level
- Auto-refresh capability
You can still use BookOtter from the command line:
pip install -r requirements.txt# Sync "Want to Read" books
python -m backend.cli
# Dry run mode
python -m backend.cli --dry-run
# Include currently reading books
python -m backend.cli --include-currently-reading
# Custom config file
python -m backend.cli --config /path/to/config.yaml
# Skip E-reader SSH test (useful for testing matching logic)
python -m backend.cli --skip-ereader-testuvicorn backend.main:app --host 0.0.0.0 --port 6887# Hardcover API Settings
hardcover:
api_token: "YOUR_API_TOKEN" # From https://hardcover.app/account/api
api_url: "https://api.hardcover.app/v1/graphql"
# Prowlarr (Search Indexer)
prowlarr:
api_key: "YOUR_PROWLARR_API_KEY"
base_url: "http://localhost:9696"
# qBittorrent (Download Client)
qbittorrent:
base_url: "http://localhost:8080"
username: "admin"
password: "YOUR_QBITTORRENT_PASSWORD"
category: "books"
# E-reader Devices (multiple supported)
ereaders:
- id: "main"
name: "My E-reader"
hostname: "ereader.tailnet"
port: 22
username: "root"
password: ""
ssh_key_path: "/root/.ssh/id_rsa"
destination_path: "/mnt/us/books/"
- id: "backup"
name: "Backup E-reader"
hostname: "ereader2.tailnet"
# ...
# Library Settings
library:
root_folders: [] # Managed via web UI
download_path: "" # Display only β qBittorrent manages actual paths
# Pipeline (Automation)
pipeline:
enabled: true
search_on_add: true # Auto-search when book added
import_on_complete: true # Auto-import when download completes
ereader_sync_on_import: true # Auto-send imports on synced shelves to E-reader
status_actions:
want_to_read:
download: true
currently_reading:
download: true
read:
download: true
# Automatic E-reader Sync
ereader_sync:
enabled: false # Sync the first configured E-reader on a schedule
interval_hours: 1 # 1, 6, or 24
# RSS Sync (Auto-Grab)
rss:
enabled: false # Enable background RSS monitoring
cron_expression: "*/15 * * * *" # How often to poll indexers
max_age_days: 3 # Only consider items newer than this
limit: 100 # Max items to fetch per indexer per poll
# Matching Settings
matching:
use_isbn: true
use_fuzzy: true
fuzzy_threshold: 80
# Sync Settings (which Hardcover shelves are imported into the library)
sync:
include_statuses:
want_to_read: true
currently_reading: false
read: false
# Transfer Settings (E-reader mirror)
transfer:
dry_run: false # Report what would be sent/deleted without doing it
folder_organization: "flat" # flat, author, series, author_series
sync_shelves: # Hardcover shelves mirrored to the E-reader
want_to_read: true
currently_reading: true
read: false
cleanup_enabled: true # Remove device books not on synced shelves or pinned
cleanup_sdr_folders: true # Also remove .sdr reading data for deleted books
cleanup_protected_paths: [] # Device paths cleanup must never touch
# Logging Settings
logging:
log_file: "bookotter.log"
log_level: "INFO"
console_output: trueThe pipeline automates the full book lifecycle. When a Hardcover sync finds books matching configured statuses, the pipeline can automatically:
- Search Prowlarr for available downloads
- Download via qBittorrent with the configured category
- Import completed downloads into the library with EPUB metadata
- Sync imported books to your E-reader (only books on shelves enabled in
transfer.sync_shelves, plus manually pinned books)
Control which statuses trigger downloads via pipeline.status_actions. Which books reach the E-reader is governed separately by transfer.sync_shelves: the E-reader mirrors those shelves β a sync sends missing shelf books and, when cleanup_enabled is on, removes everything else from the device (books sent manually via "Send to E-reader" are pinned and kept).
The RSS sync feature periodically polls your Prowlarr indexers for new releases. If a release matches a book in your library with a WANTED or MISSING status, it is automatically grabbed for download.
- First-run behavior: On the very first poll, BookOtter marks all current RSS items as "seen" without grabbing them. This prevents an accidental flood of old downloads. To backfill existing books, use the manual search workflow.
- Matching: Uses fuzzy title and author matching (respects
matching.fuzzy_threshold). - Exclusions: Currently ignores Usenet and audiobooks.
- Events: Emits
rss_sync_started,rss_sync_completed,rss_sync_failed,rss_indexer_polled,rss_match_found, andrss_grabbed.
The transfer.folder_organization setting controls how books are organized in your library and on E-reader:
| Mode | Structure |
|---|---|
flat |
All books in root folder |
author |
Author Name/book.epub |
series |
Series Name/book.epub |
author_series |
Author Name/Series Name/book.epub |
The web server exposes a REST API:
| Endpoint | Method | Description |
|---|---|---|
/api/health |
GET | Health check |
/api/library/books |
GET | List books with filtering/pagination |
/api/library/books/{id} |
GET | Get book details |
/api/library/books |
POST | Add a book |
/api/library/books/{id} |
PUT | Update book metadata |
/api/library/books/{id} |
DELETE | Delete a book |
/api/search |
GET | Search Prowlarr indexers |
/api/search/grab |
POST | Grab a search result for download |
/api/search/auto/{book_id} |
POST | Auto-search for a book |
/api/downloads |
GET | List active downloads |
/api/downloads/{id} |
DELETE | Cancel a download |
/api/root-folders |
GET/POST | Root folder management |
/api/sync/status |
GET | Get sync status |
/api/sync/hardcover |
POST | Trigger Hardcover sync |
/api/sync/ereader |
POST | Trigger E-reader sync |
/prowlarr/test |
POST | Test Prowlarr connection |
/qbittorrent/test |
POST | Test qBittorrent connection |
/prowlarr/indexers |
GET | List Prowlarr indexers |
/api/config |
GET/PUT | Configuration management |
/api/ereaders |
GET/POST | E-reader management |
/api/logs |
GET | Get log entries |
/api/ws |
WebSocket | Real-time events |
After pulling library-scanner changes, delete data/bookotter.db to rebuild the schema with the new constraints.
# Install dependencies (recommended: use uv for faster installs)
uv pip install -r pyproject.toml
# Or with pip
pip install -r requirements.txt
# Run development server
uvicorn backend.main:app --reload --host 0.0.0.0 --port 6887cd frontend
# Install dependencies
pnpm install
# Run development server (proxies API to localhost:6887)
pnpm dev
# Build for production
pnpm buildbookotter/
βββ backend/
β βββ main.py # FastAPI app
β βββ config.py # Configuration management
β βββ database.py # SQLAlchemy setup
β βββ models/ # Database models (books, downloads, etc.)
β βββ api/routes/ # API endpoints
β β βββ library.py # Book CRUD and browsing
β β βββ search.py # Prowlarr search and grab
β β βββ downloads.py # Download queue
β β βββ sync.py # Hardcover and E-reader sync
β β βββ services.py # Connection tests (Prowlarr, qBittorrent)
β β βββ root_folders.py # Root folder management
β β βββ ... # config, ereaders, logs
β βββ services/ # Business logic
β β βββ pipeline_service.py # Full automation pipeline
β β βββ pipeline_states.py # Book state machine
β β βββ hardcover_sync_service.py # Hardcover sync
β β βββ search_service.py # Search orchestration
β β βββ download_service.py # Download management
β β βββ import_service.py # EPUB import with metadata
β β βββ epub_service.py # EPUB metadata read/write
β β βββ ... # scheduler, websocket manager
β βββ clients/ # External service clients
β βββ hardcover_client.py # Hardcover GraphQL API
β βββ prowlarr_client.py # Prowlarr REST API
β βββ qbittorrent_client.py # qBittorrent Web API
β βββ ereader_client.py # E-reader SSH/SFTP
βββ frontend/
β βββ src/
β β βββ views/ # Vue page components
β β βββ stores/ # Pinia stores
β β βββ router/ # Vue Router
β βββ package.json
βββ data/ # Persistent data (mounted volume)
β βββ config.yaml
β βββ bookotter.db
β βββ bookotter.log
βββ Dockerfile
βββ docker-compose.yml
βββ config.yaml.example
- dev: Development branch for feature work and testing
- main: Production-ready code, releases are tagged from here
feature β dev β PR β main β git tag v1.x.x β Release
Docker images are automatically built and pushed to GitHub Container Registry:
| Event | Image Tags |
|---|---|
Push to dev |
ghcr.io/boren/bookotter:dev |
Push to main |
ghcr.io/boren/bookotter:main |
Tag v1.2.3 |
:1.2.3, :1.2, :1, :latest |
All pushes and PRs run automated checks:
- Backend: Python linting with Ruff
- Frontend: TypeScript type-checking and build verification
- Merge your changes from
devtomain - Create and push a semantic version tag:
git tag v1.0.0 git push --tags
- GitHub Actions will automatically:
- Build and push Docker images with version tags
- Create a GitHub Release with auto-generated changelog
Use conventional commits for automatic changelog generation:
feat:New featuresfix:Bug fixesdocs:Documentation changesrefactor:Code refactoringtest:Adding testschore:Maintenance tasks
- Verify your API tokens and keys are correct
- Check that Prowlarr and qBittorrent are running and accessible from the container
- Ensure your E-reader is connected via Tailscale and SSH is enabled
- Verify your Prowlarr instance has book indexers configured
- Test the search directly in Prowlarr's web UI to confirm indexers work
- Check that the Prowlarr API key is correct in settings
- Verify qBittorrent Web UI is enabled (Options β Web UI)
- Check username and password are correct
- If running in Docker, ensure BookOtter can reach qBittorrent's network address
- Ensure the downloaded file is a valid EPUB
- Check that the library root folder exists and is writable
- Verify volume mounts in Docker are correct
- Verify SSH credentials are correct
- Ensure destination path exists on E-reader
- Check that SSH keys are properly mounted in Docker
- Verify you have enough space on E-reader
- Ensure BookOtter can reach Prowlarr and qBittorrent instances
- If using container names, ensure they're on the same Docker network
- Use host IP or hostname accessible from the container
- Keep your
config.yamlprivate β it contains API keys and passwords - Prowlarr API key and qBittorrent password are stored in plain text in the config file
- Use SSH keys instead of passwords for E-reader connections when possible
- Hardcover API tokens expire January 1st each year
- The web UI has no authentication β run behind a reverse proxy or VPN
- Never commit
config.yamlto version control
BookOtter uses AutoAddPolicy for SSH connections, which automatically accepts host keys on first connection. This is acceptable for personal use on a trusted network (e.g., Tailscale VPN), but means:
- The first connection to a E-reader will trust its host key without verification
- You should ensure your Tailscale network is properly secured
- For high-security environments, consider pre-populating
~/.ssh/known_hosts
This project is licensed under the MIT License - see the LICENSE file for details.
- Hardcover - Book tracking platform
- Prowlarr - Indexer manager/proxy
- qBittorrent - BitTorrent client
- Tailscale - Secure network connectivity