Note: This README reflects the latest development version. For documentation specific to the latest stable release, please check the latest release on GitHub.
Automatically syncs your Audiobookshelf library with Hardcover, including reading progress, book status, and ownership information.
audiobookshelf-hardcover-sync now supports multiple sync profiles with a modern web interface and secure token management!
- π Web Management Interface: Modern, responsive web UI at
http://localhost:8080 - π₯ Multiple Sync Profiles: Each profile can have individual Audiobookshelf and Hardcover tokens
- π Secure Storage: All API tokens encrypted at rest with AES-256-GCM
- π Concurrent Syncing: Multiple profiles can sync simultaneously
- π Real-Time Monitoring: Live sync status with auto-refresh
- π§ REST API: Complete programmatic control via RESTful endpoints
- β¬οΈ Automatic Migration: Seamless upgrade from single-profile setups
- π Backwards Compatible: All existing functionality preserved
- π Cache Busting: Automatic cache invalidation ensures profiles always get the latest UI updates
-
Start the application with web UI enabled:
# Using environment variable ENABLE_WEB_UI=true ./audiobookshelf-hardcover-sync --server-only # Using config file (recommended) # Set enable_web_ui: true in your config.yaml ./audiobookshelf-hardcover-sync --server-only
-
Access the web interface: Open
http://localhost:8080in your browser -
Add Sync Profiles: Use the "Add Profile" tab to create profiles with individual tokens
-
Monitor syncs: View real-time sync status and control operations
Existing single-profile setups are automatically migrated on first startup:
- Your existing
config.yamlis detected and backed up - A "Default Profile" is created with your current configuration
- All functionality continues to work as before
- Access the new web interface at
http://localhost:8080
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Web management interface |
GET |
/api/profiles |
List all sync profiles |
POST |
/api/profiles |
Create new sync profile |
GET |
/api/profiles/{id} |
Get profile details |
PUT |
/api/profiles/{id} |
Update profile |
DELETE |
/api/profiles/{id} |
Delete profile |
PUT |
/api/profiles/{id}/config |
Update profile configuration |
GET |
/api/profiles/{id}/status |
Get sync status |
POST |
/api/profiles/{id}/sync |
Start sync |
DELETE |
/api/profiles/{id}/sync |
Cancel sync |
GET |
/api/status |
All profile statuses |
| Variable | Description | Default |
|---|---|---|
ENCRYPTION_KEY |
Base64-encoded 32-byte encryption key (auto-generated if not set) | Auto-generated |
DATA_DIR |
Directory for database and encryption files | ./data |
- Token Encryption: All API tokens encrypted at rest
- Profile Management: Full CRUD operations for sync profiles data
- Secure Key Management: Auto-generated encryption keys
- Token Masking: Sensitive data masked in API responses
- Directory Protection: Static file serving with traversal protection
The project follows standard Go project layout:
.
βββ cmd/ # Main application entry points
β βββ audiobookshelf-hardcover-sync/ # Main application
βββ internal/ # Private application code
β βββ api/ # Multi-user API handlers
β β βββ audiobookshelf/ # Audiobookshelf API client
β β βββ hardcover/ # Hardcover API client
β β βββ handlers.go # REST API endpoints
β βββ auth/ # Authentication system
β β βββ handlers.go # Auth HTTP handlers (login/logout)
β β βββ local.go # Local username/password provider
β β βββ middleware.go # Auth middleware and RBAC
β β βββ models.go # User, session, provider models
β β βββ oidc.go # Keycloak/OIDC provider
β β βββ provider.go # Auth provider interface
β β βββ repository.go # Database operations
β β βββ service.go # Auth service orchestration
β β βββ session.go # Session management
β βββ config/ # Configuration loading and validation
β βββ crypto/ # Encryption utilities
β β βββ encryption.go # AES-256-GCM token encryption
β βββ database/ # Database layer
β β βββ database.go # Connection management
β β βββ migration.go # Single-user to multi-user migration
β β βββ models.go # User, config, sync state models
β β βββ repository.go # CRUD operations
β βββ logger/ # Structured logging
β βββ models/ # Data structures
β βββ multiuser/ # Multi-user service
β β βββ service.go # User management and sync orchestration
β βββ server/ # HTTP server
β β βββ server.go # Routing and middleware setup
β βββ services/ # Business logic
β βββ sync/ # Sync logic
β βββ utils/ # Utility functions
βββ pkg/ # Public libraries
β βββ cache/ # Caching implementation
β βββ edition/ # Edition creation logic
β βββ mismatch/ # Mismatch detection
βββ web/ # Web UI assets
β βββ static/ # Static files
β βββ app.js # Multi-user management JavaScript
β βββ index.html # Web dashboard
β βββ login.html # Authentication login page
β βββ styles.css # Modern responsive styling
βββ docs/ # Documentation
β βββ AUTHENTICATION.md # Authentication setup guide
β βββ helm-chart-publishing.md # Helm deployment guide
βββ helm/ # Kubernetes Helm chart
β βββ audiobookshelf-hardcover-sync/
βββ test/ # Test files
βββ testdata/ # Test data
βββ unit/ # Unit tests
βββ integration/ # Integration tests
βββ e2e/ # End-to-end tests
- π₯ Multiple Sync Profiles: Individual Audiobookshelf and Hardcover tokens per profile
- π Web Interface: Modern, responsive management dashboard at
http://localhost:8080 - π Secure Storage: AES-256-GCM encrypted token storage
- π Concurrent Syncing: Multiple users can sync simultaneously
- π Real-Time Monitoring: Live sync status with auto-refresh
- π§ REST API: Complete programmatic control via RESTful endpoints
- β¬οΈ Automatic Migration: Seamless upgrade from single-user setups
- π Backwards Compatible: All existing functionality preserved
- Full Library Sync: Syncs your entire Audiobookshelf library with Hardcover
- Smart Status Management: Automatically sets "Want to Read", "Currently Reading", and "Read" status based on progress
- Ownership Tracking: Marks synced books as "owned" to distinguish from wishlist items
- Incremental Sync: Efficient state-based syncing to only process changed books
- Tracks sync state between runs
- Configurable minimum change threshold
- Persistent state storage
- Smart Caching: Intelligent caching of author/narrator lookups with cross-role discovery
- Enhanced Progress Detection: Uses
/api/meendpoint for accurate finished book detection, preventing false re-read scenarios
- Periodic Sync: Configurable automatic syncing (e.g., every 10 minutes or 1 hour)
- Manual Sync: HTTP endpoints for on-demand synchronization
- Health Monitoring: Built-in health check endpoint
- Configurable Logging: JSON or console (human-readable) output with configurable log levels
- Edition Creation Tools: Interactive tools for creating missing audiobook editions
- ID Lookup: Search and verify author, narrator, and publisher IDs from Hardcover database
- Container Ready: Multi-arch Docker images (amd64, arm64)
- Production Ready: Secure, minimal, and battle-tested
- Go 1.21 or later
- Docker (optional, for containerized deployment)
- Audiobookshelf instance with API access
- Hardcover API token
-
Clone the repository:
git clone https://github.com/drallgood/audiobookshelf-hardcover-sync.git cd audiobookshelf-hardcover-sync -
Install dependencies:
make deps
3.### Configuration
Create a config.yaml file based on the example:
cp config.example.yaml config.yamlEdit the configuration to match your environment. See Configuration Reference for all available options.
Deprecation Notice: The
appsection in the configuration is now deprecated and will be removed in a future version. Please migrate to the newsyncsection.
Migrating from old configuration (app.) to new configuration (sync.):
# Old deprecated format (app.*):
# app:
# sync_interval: "1h"
# minimum_progress: 0.01
# sync_want_to_read: true
# sync_owned: false
# dry_run: false
# New format (sync.*):
sync:
sync_interval: "1h"
minimum_progress: 0.01
sync_want_to_read: true
sync_owned: false
dry_run: false
# Other sync settings...The application will automatically migrate settings from the old app section to the new sync section and log a warning if any deprecated settings are found.
- Build and run the application:
make build ./bin/audiobookshelf-hardcover-sync
- Docker installed on your system
- Docker Compose (recommended for the main sync service)
- Hardcover API token
- (Optional) Audiobookshelf URL and token if using the sync service
-
Create a project directory and navigate to it:
mkdir -p ~/abs-hardcover-sync && cd ~/abs-hardcover-sync
-
Create a docker-compose.yml file:
version: '3.8' services: audiobookshelf-hardcover-sync: image: ghcr.io/drallgood/audiobookshelf-hardcover-sync:latest container_name: abs-hardcover-sync restart: unless-stopped volumes: - ./config:/app/config # For configuration files - ./data:/app/data # For persistent storage and state - ./logs:/app/logs # For log files (optional) # Optional environment variables (config file takes precedence) environment: - CONFIG_PATH=/app/config/config.yaml - LOG_LEVEL=info healthcheck: test: ["CMD", "wget", "--spider", "http://localhost:8080/healthz"] interval: 30s timeout: 10s retries: 3 start_period: 10s
-
Create a config directory and config.yaml file:
mkdir -p config && touch config/config.yaml -
Add your configuration to config.yaml:
audiobookshelf: url: https://your-abs-server.com token: your_abs_token hardcover: token: your_hardcover_token sync: interval: 10m state_file: /app/data/sync_state.json logging: level: info format: json # or text for improved readability during development
-
Create data directories:
mkdir -p data logs
-
Start the service:
docker compose up -d
-
View logs:
# Follow logs docker compose logs -f # View recent logs (last 100 lines) docker compose logs --tail=100 # View logs for a specific time period docker compose logs --since 1h
-
Common management commands:
# Stop the service docker compose down # Restart the service docker compose restart # Update to the latest version docker compose pull docker compose up -d --force-recreate
For Kubernetes deployments, use the official Helm chart:
- Add the Helm repository:
helm repo add audiobookshelf-hardcover-sync
https://drallgood.github.io/audiobookshelf-hardcover-sync/stable
helm repo add audiobookshelf-hardcover-sync-dev
https://drallgood.github.io/audiobookshelf-hardcover-sync/dev
helm repo update
2. **Create a values file** with your configuration:
```yaml
# my-values.yaml
secrets:
audiobookshelf:
url: "https://your-audiobookshelf-instance.com"
token: "your-audiobookshelf-token"
hardcover:
token: "your-hardcover-token"
# Optional: Enable persistence
persistence:
enabled: true
size: 2Gi
# Optional: Configure resources
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
- Install the chart (stable):
helm install my-sync audiobookshelf-hardcover-sync/audiobookshelf-hardcover-sync -f my-values.yaml
3b. **Install the chart (dev)**:
```bash
helm install my-sync-dev audiobookshelf-hardcover-sync-dev/audiobookshelf-hardcover-sync -f my-values.yaml
-
Check the deployment:
kubectl get pods -l app.kubernetes.io/name=audiobookshelf-hardcover-sync kubectl logs -l app.kubernetes.io/name=audiobookshelf-hardcover-sync -f
-
Upgrade the deployment:
helm upgrade my-sync audiobookshelf-hardcover-sync/audiobookshelf-hardcover-sync -f my-values.yaml
For detailed Helm chart configuration options, see the Helm Chart Documentation.
Version 3.0.0 introduces optional authentication support for securing the web UI and API endpoints.
To enable authentication with a default admin user:
# Enable authentication
export AUTH_ENABLED=true
# Set session secret (required)
export AUTH_SESSION_SECRET="your-secure-random-secret-key-here"
# Optional: Configure default admin user
export AUTH_DEFAULT_ADMIN_USERNAME="admin"
export AUTH_DEFAULT_ADMIN_EMAIL="admin@localhost"
export AUTH_DEFAULT_ADMIN_PASSWORD="changeme"After enabling authentication:
- Access the web UI at
http://localhost:8080 - Login with the default admin credentials
- Important: Change the default password immediately!
- Username/password with bcrypt hashing
- Secure session management
- Role-based access control
- OpenID Connect support
- Automatic user provisioning
- Role mapping from JWT claims
# Keycloak configuration
export KEYCLOAK_ISSUER="https://your-keycloak.example.com/realms/your-realm"
export KEYCLOAK_CLIENT_ID="audiobookshelf-hardcover-sync"
export KEYCLOAK_CLIENT_SECRET="your-client-secret"
export KEYCLOAK_REDIRECT_URI="https://your-app.example.com/auth/callback/oidc"- Admin: Full access, user management, system configuration
- User: Sync functionality, personal configurations
- Viewer: Read-only access to sync status
- HTTP-only secure cookies
- CSRF protection
- Session expiration and cleanup
- Password strength validation
- Token encryption at rest
For detailed authentication setup and configuration, see the Authentication Guide.
Version 2.0.0 introduces a YAML configuration file as the primary way to configure the application:
# Server configuration
server:
port: "8080"
shutdown_timeout: "10s" # Graceful shutdown timeout
# Rate limiting configuration
rate_limit:
rate: "1500ms" # Minimum time between requests (e.g., 1500ms for ~40 requests per minute)
burst: 2 # Maximum number of requests in a burst
max_concurrent: 3 # Maximum number of concurrent requests
# Logging configuration
logging:
level: "info" # debug, info, warn, error, fatal, panic
format: "json" # json or console
# Audiobookshelf configuration
audiobookshelf:
url: "https://your-audiobookshelf-instance.com"
token: "your-audiobookshelf-token"
# Hardcover configuration
hardcover:
# Base URL for the Hardcover GraphQL API. Defaults to the official endpoint
# https://api.hardcover.app/v1/graphql. Override if/when self-hosting is supported.
# Can also be set via HARDCOVER_BASE_URL environment variable.
base_url: ""
token: "your-hardcover-token"
# Sync settings
sync:
sync_interval: "1h"
minimum_progress: 0.01 # Minimum progress threshold (0.0 to 1.0)
sync_want_to_read: true # Sync books with 0% progress as "Want to Read"
sync_owned: true # Mark synced books as owned in Hardcover
include_ebooks: false # Include items with media type "ebook" in sync
process_unread_books: false # Process books with 0% progress for mismatches and want-to-read status
mismatch_output_dir: "./mismatches" # Directory to store mismatch JSON files
dry_run: false # Enable dry run mode (no changes will be made)
test_book_filter: "" # Filter books by title for testing
test_book_limit: 0 # Limit number of books to process for testing (0 = no limit)
# Application settings (deprecated - use sync section above)
app:
# Deprecated: These settings are moved to the 'sync' section and will be removed in a future version
sync_want_to_read: true # Deprecated: Use sync.sync_want_to_read
sync_owned: true # Deprecated: Use sync.sync_owned
dry_run: false # Deprecated: Use sync.dry_run
# Database configuration
database:
# Database type: sqlite, postgresql, mysql, mariadb
type: "sqlite"
# SQLite configuration (default)
path: "" # Uses default path if empty: ./data/database.db
# Connection pool settings
connection_pool:
max_open_conns: 25 # Maximum number of open connections
max_idle_conns: 5 # Maximum number of idle connections
conn_max_lifetime: 60 # Connection lifetime in minutes
# Authentication Configuration
authentication:
enabled: false
# Session configuration
session:
secret: "" # Auto-generated if empty
cookie_name: "audiobookshelf-sync-session"
max_age: 86400 # Session max age in seconds (24 hours)
secure: false # Set to true for HTTPS
http_only: true
same_site: "Lax"
# Default admin user (created if auth is enabled)
default_admin:
username: "admin"
email: "admin@localhost"
password: "" # Set via AUTH_DEFAULT_ADMIN_PASSWORD env var
# Keycloak/OIDC authentication (optional)
keycloak:
enabled: false
issuer: ""
client_id: ""
client_secret: "" # Set via environment variable
redirect_uri: "http://localhost:8080/auth/callback"
scopes: "openid profile email"
role_claim: "realm_access.roles"
# Sync configuration
sync:
incremental: true
state_file: "./data/sync_state.json"
min_change_threshold: 60 # seconds
libraries: # Optional library filtering
include: ["Audiobooks"] # Only sync these libraries
exclude: ["Podcasts"] # Exclude these libraries (include takes precedence)
# Paths configuration
paths:
data_dir: "./data" # Base directory for application data
cache_dir: "./cache" # Directory for cache files
mismatch_output_dir: "./mismatches" # Directory for mismatch reportsMulti-User Mode (v3.0.0+) - Recommended:
| Variable | Description | Default | Example |
|---|---|---|---|
ENCRYPTION_KEY |
Base64-encoded 32-byte encryption key | Auto-generated | base64-encoded-key |
DATA_DIR |
Directory for database and encryption files | ./data |
/app/data |
LOG_LEVEL |
Logging level | info |
debug, warn, error |
LOG_FORMAT |
Log output format | json |
json, text |
HARDCOVER_BASE_URL |
Hardcover GraphQL API base URL | https://api.hardcover.app/v1/graphql |
https://api.hardcover.app/v1/graphql |
RATE_LIMIT_RATE |
Minimum time between Hardcover API requests | unset | 1500ms, 2s |
RATE_LIMIT_BURST |
Max burst size for requests | unset | 2 |
RATE_LIMIT_MAX_CONCURRENT |
Max concurrent requests | unset | 3 |
Single-User Mode (Legacy) - For backwards compatibility (web UI disabled):
The application supports two distinct operating modes controlled by the enable_web_ui configuration option:
- Modern web interface at
http://localhost:8080 - Multi-user support with individual token management
- REST API for programmatic access
- Real-time monitoring and control
- No token requirements at startup (tokens configured via web UI)
- Backward compatible with existing setups
- Environment variable/configuration file based token management
- No web interface - runs as a service only
- Requires tokens at startup via environment variables or config file
Note: In both single-user and multi-user modes, the Hardcover client is created with a unified configuration (NewClientWithConfig), honoring
hardcover.base_urlandrate_limit.*settings.
ENABLE_WEB_UI: Enable/disable web UI (true/false, default:false)AUDIOBOOKSHELF_URL: Audiobookshelf server URL (required)AUDIOBOOKSHELF_TOKEN: Audiobookshelf API token (required for single-user mode)HARDCOVER_TOKEN: Hardcover API token (required for single-user mode)
server:
port: 8080
enable_web_ui: true # Enable web UI for multi-user mode
shutdown_timeout: 30s
# Single-user mode (when enable_web_ui: false)
audiobookshelf:
url: "https://audiobookshelf.example.com"
token: "your-audiobookshelf-token"
hardcover:
token: "your-hardcover-token"| Variable | Description | Maps to config | Notes |
|---|---|---|---|
CONFIG_PATH |
Path to config file | - | ./config.yaml |
AUDIOBOOKSHELF_URL |
URL of your AudiobookShelf instance | audiobookshelf.url |
Legacy mode only |
AUDIOBOOKSHELF_TOKEN |
AudiobookShelf API token | audiobookshelf.token |
Legacy mode only |
HARDCOVER_TOKEN |
Hardcover API token | hardcover.token |
Legacy mode only |
HARDCOVER_BASE_URL |
Hardcover API base URL | hardcover.base_url |
Override default endpoint |
RATE_LIMIT_RATE |
Min time between requests | rate_limit.rate |
e.g. 1500ms (β40 rpm) |
RATE_LIMIT_BURST |
Burst size | rate_limit.burst |
e.g. 2 |
RATE_LIMIT_MAX_CONCURRENT |
Max concurrent requests | rate_limit.max_concurrent |
e.g. 3 |
SYNC_INTERVAL |
Time between automatic syncs | sync.sync_interval |
Legacy mode only |
SYNC_INCLUDE_EBOOKS |
Include items with media type "ebook" | sync.include_ebooks |
Legacy mode only |
SYNC_LIBRARIES_INCLUDE |
Comma-separated list of libraries to include | sync.libraries.include |
Legacy mode only |
SYNC_LIBRARIES_EXCLUDE |
Comma-separated list of libraries to exclude | sync.libraries.exclude |
Legacy mode only |
π‘ Tip: For new installations, use the multi-user web interface instead of environment variables. Legacy environment variables are automatically migrated to the multi-user database on first startup.
| Container Path | Recommended Host Path | Description |
|---|---|---|
/app/config |
./config |
Configuration files |
/app/data |
./data |
Persistent data (cache, database, sync state) |
/app/logs |
./logs |
Application logs |
/tmp |
- | Temporary file storage |
| Endpoint | Method | Description |
|---|---|---|
/healthz |
GET | Basic health status |
/ready |
GET | Service readiness |
/metrics |
GET | Prometheus metrics |
The sync service supports filtering which AudioBookShelf libraries to sync. This is useful when you have multiple libraries (e.g., Audiobooks, Podcasts, Magazines) but only want to sync specific ones to Hardcover.
Sync only "Audiobooks" and "Fiction" libraries:
sync:
libraries:
include: ["Audiobooks", "Fiction"]Sync all libraries except "Magazines" and "Podcasts":
sync:
libraries:
exclude: ["Magazines", "Podcasts"]You can also use library IDs instead of names:
sync:
libraries:
include: ["lib_abc123", "lib_def456"]SYNC_LIBRARIES_INCLUDE="Audiobooks"SYNC_LIBRARIES_EXCLUDE="Magazines,Podcasts,Children's Books"- Include takes precedence: If both
includeandexcludeare specified, only theincludelist is used - Case-insensitive matching: Library names are matched case-insensitively
- ID matching: You can use either library names or library IDs
- Comma-separated: When using environment variables, separate multiple libraries with commas
- Default behavior: If no filtering is configured, all libraries are synced
To find your library names, check your AudioBookShelf web interface or look at the sync logs when filtering is disabled. The service will log all discovered libraries at the start of each sync.
- Major Rewrite: Complete architectural overhaul for improved stability and maintainability
- New: YAML configuration file support (replaces environment variables)
- Enhanced: GraphQL client for Hardcover API interactions
- Improved: Incremental sync with better state management
- New: Comprehensive logging system with multiple format options
- Enhanced: Edition information now properly handles "Unabridged" and source-specific formats
- Added: Health monitoring and metrics
- Fixed: Multiple issues with book matching and progress tracking
See the CHANGELOG.md for complete details and the MIGRATION.md guide for upgrading from previous versions.
- From v1.x to v2.0.0: Major breaking changes - refer to MIGRATION.md
- Configuration now uses YAML files (environment variables still supported but limited)
- Several environment variables have been renamed or removed
- Docker setup requires volume mapping for persistent configuration
- New logging system with configurable formats
- Reverse Proxy Users: Review the new config.yaml path settings
The project includes several utility tools to help with specific tasks:
The edition-tool helps create and manage audiobook editions in Hardcover.
# Build the tool
make build-tools
# Show help
./bin/edition-tool help
# Create an edition interactively
./bin/edition-tool create --interactive
# Create an edition prepopulated with data from Hardcover
./bin/edition-tool create --prepopulated
# Create an edition from a JSON template file
./bin/edition-tool create --file path/to/edition-template.jsonThe image-tool allows you to upload and attach cover images to books and editions in Hardcover.
# Upload an image to a book
./bin/image-tool upload --url "https://example.com/cover.jpg" --book "hardcover-book-id"
# Upload an image to an edition
./bin/image-tool upload --url "https://example.com/cover.jpg" --edition "hardcover-edition-id" --description "Audiobook Cover"
# Using a custom config file
./bin/image-tool --config /path/to/config.yaml --url "https://example.com/cover.jpg" --book "hardcover-book-id"The hardcover-lookup tool helps you search and verify author, narrator, and publisher information in Hardcover.
# Look up an author
./bin/hardcover-lookup author "Stephen King"
# Look up a narrator with JSON output
./bin/hardcover-lookup narrator "Neil Gaiman" --json
# Look up a publisher with custom results limit
./bin/hardcover-lookup publisher "Penguin Random House" --limit 10
# Get help for a specific command
./bin/hardcover-lookup help authorIf you're experiencing issues with configuration:
-
Check Configuration File Path
# Make sure CONFIG_PATH is correctly set CONFIG_PATH=/app/config/config.yaml -
Enable Debug Mode
logging: level: debug format: text # For more human-readable output
-
API Endpoint Access Ensure your AudiobookShelf token has the necessary permissions.
- Check
sync.min_progresssetting (default: 0.01 = 1%) - Verify incremental sync is working properly
- Enable debug logging to see detailed progress calculations
For additional support:
- π Check existing issues
- π Review the MIGRATION.md documentation
- π Create a new issue with debug logs if problems persist
We welcome contributions! Please see our contributing guidelines for details.
Areas for contribution:
- π§ͺ Test coverage improvements
- π Documentation enhancements
- π Bug fixes and performance improvements
- β¨ New features and integrations
- π Issues: GitHub Issues
- π Security: See SECURITY.md for vulnerability reporting
- π License: Apache 2.0
β If this project helps you, please consider giving it a star on GitHub!