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
- 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
- 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
# 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
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 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 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