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 β Kindle sync
- Library Browser: Browse your book collection with cover images, filtering, and sorting
- 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 + Kindle sync)
- Multi-Kindle Support: Configure multiple Kindle devices and choose which to sync to
- Scheduled Syncs: Configure cron-like schedules via the web UI
- 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
- Kindle with SSH access (jailbroken) connected via Tailscale
Your Kindle must be jailbroken with an SSH server running to receive book transfers. This typically involves:
- Jailbreaking your Kindle - enables running custom software
- Installing KUAL (Kindle Unified Application Launcher) - app launcher for custom apps
- Installing USBNetwork or similar - enables SSH access to your Kindle
- Setting up Tailscale (recommended) - secure network access without port forwarding
For detailed jailbreak instructions, see the MobileRead Wiki which covers most Kindle models.
Note: BookOtter uses SSH/SFTP to transfer files directly to your Kindle's filesystem. The Kindle 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 Kindle 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 Kindle 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 Kindle sync manually
- View real-time progress with WebSocket updates
- See library statistics
- Create cron-like schedules
- Enable/disable schedules
- Choose target Kindle and book statuses per schedule
- View next scheduled run time
- Configure Hardcover API token
- Configure Prowlarr and qBittorrent connections
- Manage multiple Kindle devices
- Manage root folders for library organization
- Test all connections
- Configure pipeline automation
- 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 Kindle SSH test (useful for testing matching logic)
python -m backend.cli --skip-kindle-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"
# Kindle Devices (multiple supported)
kindles:
- id: "main"
name: "My Kindle"
hostname: "kindle.tailnet"
port: 22
username: "root"
password: ""
ssh_key_path: "/root/.ssh/id_rsa"
destination_path: "/mnt/us/books/"
- id: "backup"
name: "Backup Kindle"
hostname: "kindle2.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
kindle_sync_on_import: true # Auto-sync to Kindle after import
status_actions:
want_to_read:
download: true
kindle_sync: true
currently_reading:
download: true
kindle_sync: true
read:
download: true
kindle_sync: false
# Matching Settings
matching:
use_isbn: true
use_fuzzy: true
fuzzy_threshold: 80
# Sync Settings
sync:
include_statuses:
want_to_read: true
currently_reading: false
read: false
# Transfer Settings
transfer:
dry_run: false
skip_existing: true
folder_organization: "flat" # flat, author, series, author_series
# 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 Kindle
Control which statuses trigger which actions via pipeline.status_actions. For example, you might want "read" books downloaded for your library but not automatically sent to Kindle.
The transfer.folder_organization setting controls how books are organized in your library and on Kindle:
| 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/kindle |
POST | Trigger Kindle 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/kindles |
GET/POST | Kindle management |
/api/schedules |
GET/POST | Schedule management |
/api/logs |
GET | Get log entries |
/api/ws |
WebSocket | Real-time events |
# 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 Kindle sync
β β βββ services.py # Connection tests (Prowlarr, qBittorrent)
β β βββ root_folders.py # Root folder management
β β βββ ... # config, kindles, schedules, 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
β βββ kindle_client.py # Kindle 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 Kindle 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 Kindle
- Check that SSH keys are properly mounted in Docker
- Verify you have enough space on Kindle
- 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 Kindle 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 Kindle 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