--- title: Bookshelf Backend description: Deploy Bookshelf for ebook and audiobook requests. sidebar_position: 21 --- # Bookshelf Backend SeerrNG sends book requests to a Readarr-compatible Bookshelf API. New deployments should use the Hardcover metadata backend. Existing Readarr or softcover deployments should be backed up and inventoried before cutover because Goodreads/softcover foreign IDs are provider-specific and cannot be safely reused as Hardcover IDs. [Chaptarr](https://github.com/Chaptarr/chaptarr) is a supported Readarr- compatible alternative. SeerrNG sends the selected book format explicitly, so Chaptarr can serve ebooks and audiobooks from one instance without a provider-specific URL Base or API adapter. ### Chaptarr Configure Chaptarr in **Settings > Services** as a Bookshelf server: 1. Add one service entry for each format you want to request. Set **Book Format** to **Book** or **Audiobook** to match the Chaptarr root folder and profiles selected below it. 2. Use Chaptarr's normal host, port, and API key. Leave **URL Base** blank unless you deliberately configured a URL Base in Chaptarr. 3. Select the format-specific root folder, quality profile, and metadata profile returned by the connection test. 4. Enable **Scan** after saving. Enable **Automatic Search** if approvals should start a Chaptarr search. For a single Chaptarr instance that manages both formats, create two SeerrNG service entries with the same connection details and different **Book Format** values. Mark one entry of each format as the default. A **Both** request then dispatches once to each entry. SeerrNG sends Chaptarr the requested book identity and selected-book monitoring intent. Chaptarr may still create unmonitored catalogue rows for other books by the same author; only the requested format/book is marked for monitoring. This is normal Chaptarr behaviour, not evidence that SeerrNG approved those other books. Pin the Chaptarr image to a tested version instead of relying on `latest`. Compatibility was validated against the official Docker image reporting `0.9.911.0`; Chaptarr is actively developed and its Readarr-compatible surface can change between releases. If a Chaptarr lookup is empty, first verify that the selected **Book Format** has a writable root folder and matching quality/metadata profiles. If the connection test succeeds but diagnosis reports incomplete metadata, check the Chaptarr metadata provider and retry the lookup from its UI. ## Deployment Policy ### Readarr to Softcover to Hardcover Migration Make Hardcover the default Bookshelf backend for new installs. For existing Readarr or softcover users, provide an automatic in-place migration workflow whose final result keeps the same Seerr-facing service endpoints where possible, but rebuilds book metadata against Hardcover IDs instead of blindly reusing Goodreads or softcover IDs. Core policy: - New install with no existing Readarr or Bookshelf config: create Hardcover ebook and audiobook instances. - Existing Readarr or softcover config: back up, inventory, migrate matched books to Hardcover, then disable softcover after successful migration. - Matching: strict automatic migration only; fuzzy matches go to an optional admin review report, not automatic cutover. - Softcover becomes a legacy or backup backend, not the default path. ### Installer and Compose - Change the default image from the stable `ghcr.io/snapetech/bookshelfng:softcover` image to the stable `ghcr.io/snapetech/bookshelfng:hardcover` image. - Add an installer backend mode: ```env BOOKSHELF_BACKEND=auto|hardcover|softcover ``` The default mode should be `auto`. - In `auto` mode: - if no existing config or database exists, create fresh Hardcover instances; - if an existing Readarr or softcover config/database exists, run the migration flow. - For fresh Hardcover installs: - use the stable `ghcr.io/snapetech/bookshelfng:hardcover` image, which is updated only by a release from BookshelfNG's `main` branch; - use the local rreading-glasses compatibility boundary by default; - pass `HARDCOVER_AUTH` to rreading-glasses; - enable the rreading-glasses and PostgreSQL Compose profile; - keep native Hardcover available as an explicit opt-in. - Installer reruns rewrite the generated `.env` to match the resolved backend mode. Existing `.env` files are kept as timestamped `.env.bak-*` files, and the existing `RREADING_GLASSES_POSTGRES_PASSWORD` is preserved when present. - `BOOKSHELF_METADATA_MODE` controls the metadata path: - `compatibility` (default for fresh Hardcover): local rreading-glasses and its PostgreSQL cache; - `native`: direct GraphQL from BookshelfNG, with no local proxy; - `hosted`: disable native mode and use the hosted `METADATA_URL` without a local proxy; - compatibility mode activates the rreading-glasses and PostgreSQL Compose profile, and points BookshelfNG at the local proxy. - Existing installs that have `BOOKSHELF_METADATA_URL` set to `http://127.0.0.1:*` or already use the `rreading-glasses` Compose profile are detected as `compatibility` on rerun. Set the mode explicitly to change that behavior. - Keep two instances: - ebook on `8787`; - audiobook on `8788`. ### Migration Flow Add a migration command to the installer: ```bash deploy/install-bookshelf-backend.sh --migrate-to-hardcover ``` `BOOKSHELF_BACKEND=auto` should invoke this automatically when an existing Readarr or softcover config is detected. Preflight: - verify Docker, compose, image pull, config paths, database readability, and free disk space; - require `sqlite3` when an existing `nzbdrone.db` is present, so migration does not silently produce an empty inventory; - require enough backup destination space for the existing ebook, audiobook, and rreading-glasses data paths with a default 2x margin. Override with `MIN_BACKUP_FREE_MULTIPLIER` when needed; - detect existing `config.xml`, API key, port, `nzbdrone.db`, root folders, quality profiles, metadata profiles, monitored books, editions, tags, and service type; - write a timestamped backup before stopping or changing containers. - write `backup-manifest.json` with source paths, resolved backend mode, image, metadata URL, and backup archive names. Inventory: - export every monitored book with title, author, ISBN-13, ISBN-10, ASIN, foreign IDs, selected edition, root folder, quality profile, metadata profile, tags, monitor/search flags, and service type. Matching: - query a temporary Hardcover Bookshelf instance using ISBN/ASIN first; - accept exact title plus exact author only when ISBN/ASIN is missing; - reject ambiguous, missing, or fuzzy-only matches; - write: - `migration-report.json`; - `matched-books.json`; - `unmatched-books.json`; - `ambiguous-books.json`; - `rebuild-payload.json`; - `rebuild-blocked.json`. The installer writes inventory and report files by default. If a temporary Hardcover target is already running, set `HARDCOVER_EBOOK_API_KEY` and `HARDCOVER_AUDIOBOOK_API_KEY` before migration to let the helper populate strict match reports. Override `HARDCOVER_EBOOK_BASE_URL` and `HARDCOVER_AUDIOBOOK_BASE_URL` when the temporary target is not on `127.0.0.1:8787` and `127.0.0.1:8788`. Hardcover API calls time out after 15 seconds by default; override with `HARDCOVER_API_TIMEOUT_MS`. Validation lookup uses `Foundation Isaac Asimov` by default; override with `HARDCOVER_VALIDATION_TERM` when a deployment has a better smoke-test title. For rehearsals, limit matching volume with `HARDCOVER_MIGRATION_MAX_BOOKS`. When API keys are missing or lookup cannot produce a strict match, `unmatched-books.json` still includes the source title, author, root folder, profile IDs/names, monitored state, tag labels, and identifiers for manual review. Applying the generated rebuild payload is opt-in. Set `APPLY_HARDCOVER_REBUILD=true` only after reviewing `matched-books.json`, `unmatched-books.json`, `ambiguous-books.json`, `rebuild-payload.json`, and `rebuild-blocked.json`. Applied and failed adds are written to `applied-books.json` and `apply-failures.json`. After apply, the helper writes `validation-report.json` and marks `migration-report.json` as `validation_complete` or `validation_failed` based on provider detection, lookup readiness, and whether applied book IDs are visible in the target instance. When applying matched books, source profile IDs are not treated as portable. The helper carries source quality/metadata profile names into the rebuild payload, remaps those names to target Hardcover profile IDs, and refuses an add when the target profile or root folder is missing. Source tag IDs are also treated as non-portable; the helper carries tag labels, resolves existing target tags by label, and creates missing target tags before adding matched books. Container cutover is gated by `cutover-decision.json`. Even when `ALLOW_INCOMPLETE_HARDCOVER_CUTOVER=true` is set for development, the installer refuses to continue unless validation completed successfully and no apply failures or blocked rebuild items remain. After report generation, the installer prints a concise migration summary with match counts, apply counts, validation state, and cutover readiness. To reprint that summary later: ```bash node deploy/bookshelf-hardcover-migration.mjs --summary \ /opt/bookshelf-backend/backups/20260518-145258/hardcover-migration ``` Rebuild: - create a clean Hardcover config/database using the existing API key and port where possible; - recreate root folders, profiles, metadata profiles, indexers, and download clients if they are exportable through the API or can be copied safely from database config tables; - add matched books through the Hardcover Bookshelf API, preserving monitored state, root folder, quality profile, metadata profile, tags, and search policy. Cutover: - start Hardcover ebook and audiobook containers on the original ports; - validate `/api/v1/config/development`, `/api/v1/book/lookup`, `/api/v1/book`, quality profiles, root folders, and request add/remove smoke tests; - if validation passes, disable old softcover containers/configs but keep backups; - if validation fails, restore the softcover backup and leave Seerr settings unchanged. ### SeerrNG Integration - Extend Bookshelf diagnostics to classify backend provider: - `hardcover`; - `softcover`; - `unknown`. - Surface the provider in **Settings > Services** diagnostics. - Keep softcover hydration fallback for legacy users. - Treat Hardcover as the recommended backend in docs and install paths. - Keep Readarr-compatible API behavior in SeerrNG; do not require Seerr users to know provider internals. - Add a warning when a Bookshelf service reports softcover/Goodreads metadata: `Legacy metadata backend. Hardcover is recommended for new installs.` ### Documentation Updates - Replace the current Bookshelf backend guide with: - fresh Hardcover install path; - existing Readarr/softcover migration path; - softcover legacy fallback path; - rollback instructions; - matching policy and report interpretation. - Document that direct ID reuse is unsafe: - `ForeignAuthorId`, `ForeignBookId`, and `ForeignEditionId` are provider-specific; - switching `METADATA_URL` alone is not migration. - Document unsupported v1 migration items: - uncertain or fuzzy matches are not auto-applied; - historical activity/queue data may not be preserved unless verified safe; - Goodreads import lists are not guaranteed on Hardcover. ### Test Plan Fresh install: - no existing config path creates Hardcover instances by default; - compatibility metadata is enabled by default and the rreading-glasses profile provides the shared PostgreSQL cache; - explicit `BOOKSHELF_METADATA_MODE=native` disables the proxy and uses BookshelfNG's direct Hardcover provider; - Seerr can add ebook, audiobook, and both-format requests. Legacy softcover install: - existing config triggers migration flow in `auto` mode; - backup is created before mutation; - matched books are recreated in Hardcover; - unmatched/ambiguous books are reported and not auto-migrated; - old softcover is disabled after successful validation. Rollback: - failed Hardcover validation restores original softcover config and ports; - Seerr service settings remain usable. Diagnostics: - reports Hardcover vs Softcover; - flags softcover as legacy; - confirms lookup/add readiness for Hardcover. Regression: - existing softcover users can still opt into `BOOKSHELF_BACKEND=softcover`; - existing Seerr book request tests continue passing; - zero-valued service/profile IDs remain covered. - migration helper pure logic is covered by `node --test deploy/bookshelf-hardcover-migration.test.mjs`. - migration helper match, rebuild, apply, validation, and cutover readiness are covered against a mocked Bookshelf-compatible API in the same test file. - live migration rehearsals can be run with `deploy/bookshelf-migration-lab.sh`, which copies source configs into an isolated lab, starts disposable Hardcover targets on ports `18787` and `18788`, and only imports books when run in `apply` mode. ### Assumptions - Hardcover is the default for new users. - Existing users get an automatic migration attempt, but only strict matches are applied. - Fuzzy matches are report-only for v1 and can become an admin review UI later. - After successful migration, softcover is disabled but backups are retained. - The final user-facing service should continue to look like "Bookshelf" in SeerrNG, with provider details available only in diagnostics and docs. ## Why Bookshelf The legacy `lscr.io/linuxserver/readarr` backend can be reachable and still return unusable `/api/v1/book/lookup` records. A bad lookup usually has a title and a foreign book ID, but no author object and no editions. SeerrNG refuses to add those raw records because Bookshelf/Readarr can reject them or create broken library entries. Bookshelf with Hardcover should be the default path for new installs. Bookshelf with `softcover` and `rreading-glasses` remains available as a legacy fallback for existing deployments and environments that still need Goodreads-compatible metadata. ## Architecture Run separate Bookshelf instances for ebooks and audiobooks: - `bookshelf-ebooks` on port `8787` - `bookshelf-audiobooks` on port `8788` BookshelfNG is the maintained Readarr-style application. It owns library management, download clients, importing, file organization, and the Readarr-compatible API that SeerrNG calls. rreading-glasses is a metadata compatibility/proxy layer: it exposes the metadata API BookshelfNG expects, translates requests to Hardcover, caches results in PostgreSQL, and coalesces/rate-limits upstream work. Metadata has three deliberate deployment modes: | Mode | Bookshelf metadata path | rreading-glasses | Use it when | | --- | --- | --- | --- | | `compatibility` (default for fresh Hardcover) | BookshelfNG → local rreading-glasses → upstream | On, with PostgreSQL | A shared durable cache, centralized throttling, or a compatibility boundary | | `native` (explicit opt-in) | BookshelfNG → Hardcover GraphQL | Off | The shortest direct Hardcover path | | `hosted` | BookshelfNG → `METADATA_URL` such as `https://hardcover.bookinfo.pro` | Off | Native credentials are unavailable or a hosted compatibility endpoint is preferred | Compatibility is the installer default because it keeps BookshelfNG decoupled from Hardcover's GraphQL API and centralizes Hardcover authentication, caching, request coalescing, and upstream throttling. One proxy and PostgreSQL cache can serve both the ebook and audiobook instances. Existing local proxy installs are detected and preserved on rerun. Native remains an explicit alternative when the shortest direct path is more valuable than the shared proxy boundary. Select it with `BOOKSHELF_METADATA_MODE=native`. ### Native Hardcover failure behavior Native mode intentionally has no hidden runtime failover. The request path is: 1. BookshelfNG checks its in-process metadata cache. 2. On a miss, it sends the GraphQL request directly to Hardcover using `HARDCOVER_AUTH`. 3. A successful response is mapped into BookshelfNG's normal metadata models and cached for later lookups. 4. If native mode is disabled, the same application falls back to `METADATA_URL`; switching to hosted or compatibility mode is explicit and visible in `.env`. This avoids a partially working deployment that silently changes providers or duplicates requests during an outage. The tradeoff is that native mode does not provide rreading-glasses' durable cross-instance cache. A fresh search or refresh still needs Hardcover, and an operator must select compatibility mode if persistent proxy caching is required. ### Compatibility-mode outage behavior In compatibility mode, rreading-glasses provides the older shared path: 1. BookshelfNG calls the local rreading-glasses endpoint on port `8790`. 2. rreading-glasses checks its in-memory cache and then its PostgreSQL-backed cache on localhost port `15433`. 3. Cached author, work, edition, or series responses can be served without a fresh upstream call until their cache lifetime expires. 4. A cache miss, expired entry, or free-text search still needs the configured upstream. The proxy is not an unlimited offline mirror. The shared cache can help both Bookshelf instances and survives a proxy restart when its PostgreSQL volume is healthy. It also adds two local failure points: the proxy and its database. If either is unavailable, both instances lose this metadata path. There is no automatic runtime failover from Hardcover to Goodreads or OpenLibrary in either mode. During a compatibility-mode outage, keep rreading-glasses and `RREADING_GLASSES_POSTGRES_DIR` intact. The installer backs up that directory when it exists. The proxy readiness check proves only that the local process is serving; use `--validate-api`, an actual lookup, and the proxy logs to verify upstream recovery. For compatibility mode, provide `HARDCOVER_AUTH` with the `Bearer ` prefix; the token is used by rreading-glasses. Native mode passes it to BookshelfNG. Get a token from https://hardcover.app/settings → Hardcover API. Hosted mode does not require a local token for metadata, although Bookshelf's own Hardcover list-import settings still need an API key when that feature is used. For Goodreads/softcover mode, provide a Goodreads cookie via `COOKIE` if your upstream requires one. If Bookshelf is reachable but searches return no books, run the installer's `--validate-api` check and inspect the lookup response, not just the TCP connection test. Hardcover mode requires `HARDCOVER_AUTH` with the `Bearer ` prefix; softcover mode requires the Goodreads `COOKIE` when the upstream asks for it. Bookshelf supports only one type of a given book in a single instance. SeerrNG therefore expects one default Bookshelf service for ebooks and a separate default Bookshelf service for audiobooks when audiobook or both-format requests are used. ## Repository and Image The public Snapetech fork is: ```text https://github.com/snapetech/bookshelfng ``` If Docker is not an option, follow the [BookshelfNG source-build guide](./bookshelf-source-build.md) to compile and run the backend directly on Linux. The deployment compose defaults to the Snapetech image: ```text ghcr.io/snapetech/bookshelfng:hardcover ``` The installer and deployment Compose file use the stable `hardcover` tag. BookshelfNG updates that tag only from its `main` release workflow, so SeerrNG does not consume `develop` images by default. Set `BOOKSHELF_IMAGE` to a full digest reference when reproducible upgrades and rollbackability matter more than automatic adoption of the newest main release. That image is built from the public `bookshelfng` fork and provides the native Hardcover metadata backend. Fresh installs use the local rreading-glasses compatibility path by default; set `BOOKSHELF_METADATA_MODE=native` to pass `HARDCOVER_AUTH` directly to BookshelfNG. To force the legacy softcover path, set `BOOKSHELF_BACKEND=softcover`; this switches the image to the stable `ghcr.io/snapetech/bookshelfng:softcover` and keeps compatibility mode. Softcover deployments use `COOKIE` when the Goodreads upstream requires it. The image is published from GitHub Actions in the `snapetech/bookshelfng` repository: ```bash docker pull ghcr.io/snapetech/bookshelfng:softcover ``` If a pull returns `denied`, the package exists but is not anonymously readable. Make it public in GitHub under **Packages > bookshelfng > Package settings > Change visibility**, or authenticate Docker with a token that can read packages. As a temporary fallback, set `BOOKSHELF_IMAGE=ghcr.io/pennydreadful/bookshelf:softcover`; SeerrNG can hydrate upstream softcover lookup results that contain `foreignEditionId` but return an empty `editions` array. ## Known Good Versions The validated SeerrNG backend migration build was: ```text seerrng:bookshelf-fix-20260518f ``` The validated BookshelfNG fork includes these changes: ```text bde894431 Return author and editions from book lookup c416ad1af Add image publish workflow d7dc505f8 Skip CI for documentation-only changes 0d600a564 Guard null Hardcover edition sub-objects fd8abff6b Add sparse Hardcover metadata regression tests 5788f7c1c Pin patched transitive dependencies ``` Use the stable `ghcr.io/snapetech/bookshelfng:softcover` image for softcover once the GHCR package is public or the Docker host is authenticated. If you need a fully anonymous pull before that package visibility is corrected, use `ghcr.io/pennydreadful/bookshelf:softcover` and rely on SeerrNG's softcover hydration fallback. ## Paths Keep the same media and download mount paths that download clients and Plex already know about. If these paths change, imports and hardlinks can break. The example compose uses: ```text /data -> media root /download -> download root /downloads -> media root compatibility mount /plex -> Plex library root /media/plex -> Plex library root compatibility mount ``` For the validated deployment, the important in-container paths were: ```text /data/plex/books /download/books ``` ## Installer Script The repository includes an installer helper: ```bash deploy/install-bookshelf-backend.sh ``` For existing Readarr or softcover deployments, use the full migration runbook: ```text docs/using-seerr/bookshelf-hardcover-migration.md ``` It does the following: - validates Docker, Docker Compose, the compose template, and the Bookshelf image, - copies `deploy/compose.bookshelf.yml` into an install directory, - writes an `.env` file with a generated Postgres password, - backs up existing Bookshelf/Readarr config directories, - creates missing config/data directories, - creates or patches each Bookshelf `config.xml` so the ebook instance binds port `8787` and the audiobook instance binds port `8788`, - optionally stops an old Readarr container, - starts the two Bookshelf instances with Docker Compose; starts rreading-glasses and Postgres only when `BOOKSHELF_METADATA_MODE=compatibility`, - prints the Seerr settings and validation commands. Preview the install without changing files or containers: ```bash sudo deploy/install-bookshelf-backend.sh --dry-run ``` Validate an already-rendered install directory without starting containers: ```bash sudo deploy/install-bookshelf-backend.sh --validate-only ``` Validate the Bookshelf APIs after startup: ```bash sudo EBOOK_API_KEY=replace-me AUDIOBOOK_API_KEY=replace-me \ deploy/install-bookshelf-backend.sh --validate-api --skip-pull --no-stop-readarr ``` Run it on the Docker host: ```bash sudo INSTALL_DIR=/opt/bookshelf-backend \ BOOKSHELF_EBOOKS_CONFIG_DIR=/mnt/datapool_lvm_media/readarr-config \ BOOKSHELF_AUDIOBOOKS_CONFIG_DIR=/mnt/datapool_lvm_media/bookshelf-audiobooks-config \ MEDIA_ROOT=/mnt/datapool_lvm_media \ DOWNLOAD_ROOT=/mnt/datapool_lvm_media/download \ PLEX_ROOT=/mnt/datapool_lvm_media/plex \ deploy/install-bookshelf-backend.sh ``` To stop an existing Readarr container during migration: ```bash sudo STOP_OLD_READARR_CONTAINER=readarr-host deploy/install-bookshelf-backend.sh ``` To keep an existing Readarr container running while preparing the new stack: ```bash sudo STOP_OLD_READARR_CONTAINER=readarr-host \ deploy/install-bookshelf-backend.sh --no-stop-readarr ``` To use the upstream image instead of the Snapetech fork: ```bash sudo BOOKSHELF_IMAGE=ghcr.io/pennydreadful/bookshelf:softcover \ deploy/install-bookshelf-backend.sh ``` If the image is already present locally or the registry requires authentication, skip the pull step: ```bash sudo deploy/install-bookshelf-backend.sh --skip-pull ``` ## Manual Compose Use the included compose file if you prefer manual setup: ```bash mkdir -p /opt/bookshelf-backend cp deploy/compose.bookshelf.yml /opt/bookshelf-backend/compose.yml cd /opt/bookshelf-backend ``` Create `/opt/bookshelf-backend/.env`: ```env PUID=1000 PGID=953 TZ=America/Regina BOOKSHELF_BACKEND=hardcover BOOKSHELF_IMAGE=ghcr.io/snapetech/bookshelfng:hardcover BOOKSHELF_METADATA_MODE=compatibility BOOKSHELF_METADATA_URL=http://127.0.0.1:8790 BOOKSHELF_HARDCOVER=true BOOKSHELF_HARDCOVER_NATIVE=false BOOKSHELF_HARDCOVER_AUTH= BOOKSHELF_EBOOKS_CONFIG_DIR=/mnt/datapool_lvm_media/readarr-config BOOKSHELF_AUDIOBOOKS_CONFIG_DIR=/mnt/datapool_lvm_media/bookshelf-audiobooks-config MEDIA_ROOT=/mnt/datapool_lvm_media DOWNLOAD_ROOT=/mnt/datapool_lvm_media/download PLEX_ROOT=/mnt/datapool_lvm_media/plex COMPOSE_PROFILES=rreading-glasses HARDCOVER_AUTH=Bearer your-hardcover-api-token-here RREADING_GLASSES_UPSTREAM=api.hardcover.app RREADING_GLASSES_IMAGE=blampe/rreading-glasses:hardcover@sha256:3489e722a73c9cbab5b9ba530cf8a60c2280367fb03db1fb649261dfb064b52f RREADING_GLASSES_POSTGRES_DIR=/mnt/datapool_lvm_media/rreading-glasses-postgres/data RREADING_GLASSES_POSTGRES_PASSWORD=replace-with-a-long-random-password ``` For explicit native mode, use the same file with these metadata settings and a token passed directly to BookshelfNG: ```env BOOKSHELF_METADATA_MODE=native BOOKSHELF_METADATA_URL=https://hardcover.bookinfo.pro BOOKSHELF_HARDCOVER_NATIVE=true BOOKSHELF_HARDCOVER_AUTH=Bearer your-hardcover-api-token-here COMPOSE_PROFILES= HARDCOVER_AUTH=Bearer your-hardcover-api-token-here ``` For Goodreads/softcover compatibility mode, use: ```env BOOKSHELF_BACKEND=softcover BOOKSHELF_IMAGE=ghcr.io/snapetech/bookshelfng:softcover BOOKSHELF_METADATA_MODE=compatibility BOOKSHELF_METADATA_URL=http://127.0.0.1:8790 BOOKSHELF_HARDCOVER_NATIVE=false COMPOSE_PROFILES=rreading-glasses RREADING_GLASSES_IMAGE=blampe/rreading-glasses:latest@sha256:dd996a1db19ac4ef18df47f1671f608c0f097ed43c4776ebde94dee20c6b43c8 RREADING_GLASSES_UPSTREAM=www.goodreads.com COOKIE=your-goodreads-cookie-here RREADING_GLASSES_POSTGRES_PASSWORD=replace-with-a-long-random-password ``` Start the stack: ```bash docker compose pull docker compose up -d ``` For a fresh manual setup, make sure each Bookshelf config directory has a different port in `config.xml` before both containers run on host networking: ```xml 8787 8788 ``` If both instances bind `8787`, only one container can start successfully. ## Backups Before replacing Readarr or moving config directories, back up: - ebook Bookshelf/Readarr config directory, - audiobook Bookshelf config directory, if it already exists, - rreading-glasses Postgres data directory, if it already exists. Example: ```bash backup_dir=/mnt/datapool_lvm_media/backups/readarr-bookshelf-$(date +%Y%m%d-%H%M%S) mkdir -p "$backup_dir" tar -C /mnt/datapool_lvm_media -czf "$backup_dir/readarr-config.tgz" readarr-config tar -C /mnt/datapool_lvm_media -czf "$backup_dir/bookshelf-audiobooks-config.tgz" bookshelf-audiobooks-config ``` Do not delete the old backup after first boot. Keep it until ebook, audiobook, and both-format requests have been tested through SeerrNG. ## SeerrNG Configuration In **Settings > Services**, add two Bookshelf services. Book service: ```text Hostname: kspls0, 127.0.0.1, or the Docker host name reachable by SeerrNG Port: 8787 Book Format: Book Quality Profile: eBook Root Folder: /data/plex/books Default Server: enabled Enable Scan: enabled Enable Automatic Search: your policy ``` Audiobook service: ```text Hostname: kspls0, 127.0.0.1, or the Docker host name reachable by SeerrNG Port: 8788 Book Format: Audiobook Quality Profile: Spoken Root Folder: /data/plex/books, unless you maintain a separate audiobook root Default Server: enabled Enable Scan: enabled Enable Automatic Search: your policy ``` Use each instance's own API key from **Bookshelf > Settings > General > Security**. Do not reuse a stale key unless the config directory was intentionally migrated and the key is still valid. ## Metadata Source Each Bookshelf instance should use: ```text http://127.0.0.1:8790 ``` Verify with: ```bash curl -H "X-Api-Key: EBOOK_API_KEY" \ http://127.0.0.1:8787/api/v1/config/development curl -H "X-Api-Key: AUDIOBOOK_API_KEY" \ http://127.0.0.1:8788/api/v1/config/development ``` Both responses should include: ```json { "metadataSource": "http://127.0.0.1:8790" } ``` ## Lookup Validation Run these against the Docker host: ```bash curl -H "X-Api-Key: EBOOK_API_KEY" \ "http://127.0.0.1:8787/api/v1/book/lookup?term=Foundation%20Isaac%20Asimov" curl -H "X-Api-Key: AUDIOBOOK_API_KEY" \ "http://127.0.0.1:8788/api/v1/book/lookup?term=Foundation%20Isaac%20Asimov" curl -H "X-Api-Key: EBOOK_API_KEY" \ "http://127.0.0.1:8787/api/v1/author/lookup?term=J.R.R.%20Tolkien" ``` With the stable `ghcr.io/snapetech/bookshelfng:softcover` image, lookup results should include nested `author` metadata and at least one `editions` entry. If you use upstream Bookshelf and see `editions: []`, SeerrNG can still hydrate results that include `foreignEditionId` and resolvable author metadata, but the Snapetech image is the recommended fix. ## SeerrNG Diagnostic The Bookshelf settings modal includes **Run Diagnostic**. It checks: - backend unreachable, - lookup returned no results, - lookup returned incomplete/unusable results, - backend rejected an add test, - lookup is usable. The API endpoint is: ```http POST /api/v1/settings/readarr/diagnose ``` With a valid authenticated admin session, the body is the same Bookshelf settings shape used by the service modal. Add `"testAdd": true` when you want SeerrNG to attempt an add and immediately remove the test book without deleting files. ## Request Validation After configuring both services: 1. Request an ebook for a known addable book. 2. Request an audiobook for a known addable book. 3. Request both formats for a known addable book. 4. Confirm the ebook request lands in `bookshelf-ebooks`. 5. Confirm the audiobook request lands in `bookshelf-audiobooks`. 6. Confirm each item uses the expected root folder and quality profile. 7. Confirm SeerrNG marks the request `COMPLETED`. ## Migration Lab Use the lab runner to rehearse migration without writing to the source Bookshelf/Readarr config. It copies source config directories into `.bookshelf-migration-lab/source`, starts disposable Hardcover targets on `18787` and `18788`, and writes migration reports under `.bookshelf-migration-lab/backups`. Discover visible local candidates: ```bash deploy/bookshelf-migration-lab.sh discover ``` Report-only rehearsal: ```bash SOURCE_EBOOK_CONFIG_DIR=/path/to/readarr-or-bookshelf-config \ HARDCOVER_MIGRATION_MAX_BOOKS=50 \ deploy/bookshelf-migration-lab.sh report ``` Optional audiobook source: ```bash SOURCE_EBOOK_CONFIG_DIR=/path/to/ebook-config \ SOURCE_AUDIOBOOK_CONFIG_DIR=/path/to/audiobook-config \ deploy/bookshelf-migration-lab.sh report ``` Import strict matches into the disposable lab target only: ```bash SOURCE_EBOOK_CONFIG_DIR=/path/to/ebook-config \ APPLY_IMPORT=true \ deploy/bookshelf-migration-lab.sh apply ``` Print the latest lab report counts: ```bash deploy/bookshelf-migration-lab.sh summary ``` Stop or remove the lab: ```bash deploy/bookshelf-migration-lab.sh down deploy/bookshelf-migration-lab.sh clean ``` If GHCR access is unavailable, the lab falls back to a local Bookshelf image when one is present. It can also build a local Hardcover image from `/home/keith/Documents/code/bookshelfng` when the base image is available. ## Rollback If the migration fails: 1. Restore the generated backup: ```bash sudo BACKUP_DIR=/opt/bookshelf-backend/backups/20260518-145258 \ deploy/install-bookshelf-backend.sh --restore-backup ``` The restore command stops the rendered compose stack if `/opt/bookshelf-backend/compose.yml` exists, moves current config directories aside with a `.pre-restore-YYYYMMDD-HHMMSS` suffix, restores any backup tarballs present in `BACKUP_DIR`, and leaves Seerr settings unchanged. It can restore tarballs even when Docker is unavailable; in that case it skips compose shutdown and reports that explicitly. 2. Start the old Readarr container with the original mounts. 3. In SeerrNG, point the ebook service back to the old Readarr endpoint or disable book requests until Bookshelf is corrected. Manual rollback is also possible: 1. Stop the Bookshelf stack: ```bash cd /opt/bookshelf-backend docker compose down ``` 2. Restore the backed-up ebook config directory: ```bash rm -rf /mnt/datapool_lvm_media/readarr-config tar -C /mnt/datapool_lvm_media -xzf /path/to/readarr-config.tgz ``` 3. Restore the audiobook config directory if it was changed: ```bash rm -rf /mnt/datapool_lvm_media/bookshelf-audiobooks-config tar -C /mnt/datapool_lvm_media -xzf /path/to/bookshelf-audiobooks-config.tgz ``` 4. Restore rreading-glasses Postgres data if you need to preserve its cache: ```bash rm -rf /mnt/datapool_lvm_media/rreading-glasses-postgres/data tar -C /mnt/datapool_lvm_media/rreading-glasses-postgres \ -xzf /path/to/rreading-glasses-postgres.tgz ``` Rollback does not require changing SeerrNG code. It only changes service settings and backend containers. ## Troubleshooting `backend_unreachable`: - confirm the container is running, - confirm SeerrNG can reach the host and port, - confirm the API key is correct, - confirm URL Base is empty unless Bookshelf is configured with one. `lookup_empty`: - check `BOOKSHELF_METADATA_MODE` in the generated `.env`; - in native mode, confirm the Bookshelf image contains the native provider and `BOOKSHELF_HARDCOVER_AUTH` is present; - in hosted mode, confirm `BOOKSHELF_METADATA_URL` is reachable; - in compatibility mode, confirm rreading-glasses is running and Bookshelf `metadataSource` is `http://127.0.0.1:8790`; - try an ISBN lookup such as `isbn:9780547928227`. `lookup_incomplete`: - confirm `/api/v1/author/lookup` works for the author, - confirm the lookup result has `foreignEditionId`, - update SeerrNG to a build that includes Bookshelf softcover hydration. `backend_add_rejected`: - check Bookshelf logs, - confirm root folder and quality profile exist in that Bookshelf instance, - confirm the selected root folder is writable inside the container, - confirm download client paths match the container mounts. Bulk request returns `serverId must be a positive integer`: - update SeerrNG to a build that accepts zero-valued Servarr IDs in request overrides, - confirm the service still exists in **Settings > Services**, - retry the request after reloading the browser. This can affect the first configured Bookshelf or Lidarr service because SeerrNG stores service IDs starting at `0`. The backend must treat `0` as a valid service/profile override value, not as a missing or invalid ID. ## Current Limitations - Bulk book and music requests can still take a long time on large discographies or bibliographies. The modal shows submit progress and can retry failed items, but it does not yet offer a per-item backend preflight before submission. - Music discography selection defaults to albums and can be filtered by release type. Review the selection before requesting singles, live albums, compilations, or other secondary release groups. - Book requests rely on Bookshelf-compatible lookup metadata. The diagnostic can identify incomplete lookup results, but it does not yet run automatically before every request. - Both-format book requests dispatch to two backend services. Check each Bookshelf instance when troubleshooting partial success.