Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Clean-room Go rewrite, modern React UI, MIT-licensed, actively developed.

| | **Bindery** | LazyLibrarian | Livrarr | Readarr |
|---|---|---|---|---|
| **Status** | Active (v1.22.x) | Active (community fork) | Alpha (0.1.x) | Archived (June 2025) |
| **Status** | Active (v1.37.x) | Active (community fork) | Alpha (0.1.x) | Archived (June 2025) |
| **Stack** | Go, single binary | Python | Rust | C# / .NET |
| **Book metadata** | 6 independent sources, no scraping | Goodreads + fallbacks | 4 sources, no scraping | Dead backend |
| **Readarr `.db` import** | **Yes** | No | No | — |
Expand All @@ -108,7 +108,7 @@ Clean-room Go rewrite, modern React UI, MIT-licensed, actively developed.
**Pick Livrarr** if you want the same single-binary spirit and don't need the maturity, the Readarr import, Pi-class ARM, or a Helm chart yet.
**Calibre-Web / Audiobookshelf** solve a different problem — *serving* an already-curated library, not automating acquisition — and pair well downstream of Bindery.

<sub>Competitor details verified June 2026 (Readarr archived 2025-06-27; LazyLibrarian active community fork; [Livrarr](https://github.com/kkodecs/livrarr) v0.1.0-alpha5). Spotted something out of date? Open an issue.</sub>
<sub>Competitor details verified June 2026 (Readarr archived 2025-06-27; LazyLibrarian active community fork; [Livrarr](https://github.com/kkodecs/livrarr) v0.1.0-alpha5). Livrarr rechecked September 2026: v0.1.0-alpha6. Spotted something out of date? Open an issue.</sub>

## Features

Expand All @@ -121,6 +121,7 @@ Clean-room Go rewrite, modern React UI, MIT-licensed, actively developed.
- **Library adoption**: whatever the scan could not match waits on the Import page as one row per book (an audiobook folder or disc set is one row), with suggested matches (one click Confirm only for a strong match), one "Add author" decision per missing author, and Undo. Files are registered where they are, never moved.
- Author aliases (`RR Haywood` / `R.R. Haywood` / `R R Haywood` merge into one canonical row), and metadata re-bind to correct a wrong match without delete-and-re-add. On a book’s **File → Re-bind** dialog, search configured metadata providers and select an OpenLibrary work or Hardcover book, or enter an exact provider ID and press Enter. Results already in the library are marked and link to the existing book; records belonging to another library book cannot be selected. Each result’s **Links** menu opens its upstream page when available; OpenLibrary editions can be viewed but require a work ID for re-binding. Re-binding updates metadata and series without moving files; author mismatches require confirmation.
- Explicit author-catalogue reconciliation with a selectable preview: remove chosen stale metadata-only Wanted rows after changing provider or metadata profile while always protecting imported books and every row with a tracked file.
- Manual metadata editing with field locks: edit a book's title, description, genres, language or release date and the edit survives every refresh. **Fix match** reassigns a file that was attached to the wrong book. See [docs/Metadata-Editing-Wiki.md](docs/Metadata-Editing-Wiki.md).

**Search & downloads**
- Newznab + Torznab indexers queried in parallel, deduplicated, then composite-ranked by format quality, edition tags (RETAIL / UNABRIDGED / ABRIDGED), year match, grab count, size, and ISBN exact-match bonus.
Expand All @@ -129,11 +130,13 @@ Clean-room Go rewrite, modern React UI, MIT-licensed, actively developed.
- **Diagnose** on each saved download client: one click checks the connection, the category, where ebook and audiobook grabs actually land, the path remap, whether Bindery can read that folder, and whether imports can hardlink into each library folder, then names the first thing to fix. No shell needed, which matters in the distroless image.
- Auto-grab sweep every 12h, immediate search on add or `wanted` flip, plus interactive per-book search and "Search all wanted" per author. Global kill-switch pauses auto-grab without losing your monitored list.
- Quality profiles covering every format release parsing recognises (EPUB, MOBI, AZW3, PDF, plus AZW, DJVU, CBR, CBZ, FB2, LIT, RTF, TXT and the audio containers M4B, M4A, FLAC, MP3, OGG), language filter, regex-based custom formats, delay profiles, blocklist (consulted on every search; one-click add from History), and failure visibility in Queue and History.
- Indexer-friendly by default: an optional per-indexer **Daily query limit** with usage shown on the Indexers tab, a rate-limited indexer is left alone for longer each time it refuses (one hour, then three, six, twelve and a day) and its row says when searches resume, an indexer that has stopped working is flagged in Settings, and a per-indexer *freeleech only* policy holds ratio-costing releases for manual approval.

**Import & organize**
- Completed downloads matched by NZO ID and placed in the library with configurable naming. Modes: **Auto** (default — hardlink when possible, else copy; seeding-safe), **Move**, **Copy** (keep source for seeding), **Hardlink** (zero extra disk; same filesystem required), **External** (hand off to a sibling tool).
- Naming tokens — `{Author}`, `{SortAuthor}`, `{Title}`, `{Year}`, `{Series}`, `{SeriesNumber}`, `{Genre}`, `{Lang}`, `{ext}` — collapse cleanly for non-series books, with conditional literals (`{Title}{ - Series}` emits the dash only when a series exists) and zero-pad widths (`{SeriesNumber:2}` → `02`).
- Cross-filesystem-safe moves: atomic rename when possible, copy + verify + delete for NFS / separate volumes. Full grab / import / failure history per book.
- The **Import** page is where files you already have come in, two ways: **In your library** is the adoption list described above, and **From a folder** points at a folder anywhere Bindery can read, matches what it finds and imports it into the library, creating a book from a metadata search when nothing in the catalogue fits.
- Calibre integration in three modes: `calibredb` CLI hook on import, [Bindery Bridge plugin](https://github.com/vavallee/bindery-plugins) (cross-container), or direct read of an existing Calibre library's `metadata.db` as Bindery's catalogue.
- **Audiobookshelf import** — pull an existing ABS server's book libraries in as Bindery's catalogue (metadata-first, dry-run, review queue for ambiguous matches, rollback), with an ABS library-scan trigger after every audiobook import. See [docs/ABS-Import-Wiki.md](docs/ABS-Import-Wiki.md).
- **Grimmory push** (preview) — imported ebooks are sent to a self-hosted [Grimmory](https://grimmory.org) library via its BookDrop inbox, with a bulk **Push all** for existing files.
Expand Down Expand Up @@ -162,10 +165,13 @@ Cover images are fetched and cached server-side under `<dataDir>/image-cache/` (
- **Multi-user mode** — per-user libraries, monitored authors, profiles, and downloads. Admin role manages indexers / download clients / users; standard users see only their own catalogue. Local, OIDC-provisioned, or forward-auth-mapped.
- **Requests** (requester role). Give family and friends an account that can browse the library read only and ask for a book or an author, but cannot grab, download, delete or configure anything. Admins approve from a Requests queue with the usual add choices, and the added books belong to the person who asked. Requesters can follow each request to "available", and a webhook can announce new ones. See [docs/multi-user.md](docs/multi-user.md#requester).
- **Webhook notifications** for grab / import / failure / new books found (pipe to Apprise, ntfy, Home Assistant, Discord, Slack via proxies). **On-demand SQLite backups.** **Persistent log viewer** in Settings → Logs with runtime DEBUG toggle.
- **Download logs** from Settings → Logs as a text file for bug reports. **In-app update badge** when a newer release exists. `bindery db-check` and `bindery db-repair` report and repair orphaned database rows offline, for an instance that will not start ([details](docs/DEPLOYMENT.md)).
- **Arr-compatible queue** at `GET /api/queue` for [Harpoon](https://github.com/harpoon-io/harpoon) and other *arr-aware tools — pagination, sort, live size, status, client, remote ID, protocol.

**UI**
- Modern React 19 + TypeScript + Tailwind CSS SPA with search-first author acquisition and deep-linkable routed `/book/:id` and `/author/:id` pages.
- Five entries in the top bar: **Library** (Authors, Books, Series), **Activity** (Wanted, Queue, History, and Requests for an admin), **Import**, **Calendar** and **Discover**. A group opens on its first page and repeats the rest as tabs above the content, and every page keeps the address it always had.
- Library search in the header over your authors, books and series, with a last row that hands a miss to a single **Add to library** dialog for authors and books (title, author, ISBN or ASIN).
- Light / dark themes (respecting `prefers-color-scheme` first paint), grid / table view toggles, mobile-friendly responsive layout, hamburger nav, agenda-style mobile Calendar.
- Full pagination, search, filter, and sort on every list page; preferences persist to `localStorage`.
- Previous/Next navigation between authors on the author detail page, stepping through the list page you came from.
Expand Down Expand Up @@ -239,6 +245,9 @@ The full reference (path remapping, API-key seeding, telemetry, trusted-proxy, r
| **Usenet clients** | SABnzbd, NZBGet |
| **Torrent clients** | qBittorrent, Transmission, Deluge, rTorrent / ruTorrent (XML-RPC over HTTP or SCGI) |
| **Indexers** | Newznab (NZBGeek, NZBFinder, NZBPlanet, DrunkenSlug, …), Torznab (Prowlarr, Jackett, direct endpoints), with per-indexer category overrides |
| **Metadata sources** | OpenLibrary, Google Books, Hardcover, DNB, Audnex, Audible |
| **Import lists** | Hardcover lists, synced on a configurable interval |
| **Library tools** | Calibre (`calibredb`, Bindery Bridge plugin, `metadata.db` import), Audiobookshelf (import and scan trigger), Grimmory (preview), post-import drop folder for Calibre-Web-Automated and similar |
| **Notifications** | Generic webhooks — pipe to Apprise / ntfy / Home Assistant / Slack / Discord |
| **Authentication** | Local (argon2id), API key, OIDC (Google, GitHub via Dex, Authelia, Keycloak, …), forward-auth proxy |
| **Reading apps** | OPDS 1.2 catalogue at `/opds/` (KOReader, Moon+ Reader, Aldiko, …) |
Expand Down Expand Up @@ -293,6 +302,7 @@ The full endpoint catalogue, authentication rules (API key, session cookie, loca
| **Deployment** — Docker, Compose, k8s/Helm, binary, UID/GID, env vars, upgrades | [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) |
| **Architecture** — components, data flow, dependencies | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
| **API** — REST endpoints, auth, integration patterns | [docs/API.md](docs/API.md) |
| **Search and matching design** (contributors): how titles and author names are normalised and compared | [docs/search-design.md](docs/search-design.md) |
| **Roadmap** — planned work and explicitly-out-of-scope items | [docs/ROADMAP.md](docs/ROADMAP.md) |
| **Multi-user** — roles, user management, CSRF tokens | [docs/multi-user.md](docs/multi-user.md) |
| **Upgrading to v1.0** — multi-user migration: backup, dry-run, rollback | [docs/upgrade-v1.md](docs/upgrade-v1.md) |
Expand All @@ -313,7 +323,7 @@ The full endpoint catalogue, authentication rules (API key, session cookie, loca

## Community

- **Discord** — real-time help, setup questions, release chat: [discord.gg/RpuYYRM9cZ](https://discord.gg/RpuYYRM9cZ). The `#support` channel is the best place to ask; `#changelog` is updated on every release. The three read-only voice channels at the top of the server show live active-install count, latest release, and GitHub star count, refreshed from the telemetry API every 10 minutes.
- **Discord** — real-time help, setup questions, release chat: [discord.gg/RpuYYRM9cZ](https://discord.gg/RpuYYRM9cZ). The `#support` channel is the best place to ask; `#releases` is updated on every release. The three read-only voice channels at the top of the server show live active-install count, latest release, and GitHub star count, refreshed from the telemetry API every 10 minutes.
- **GitHub Issues** — bug reports and feature requests: [issues](https://github.com/vavallee/bindery/issues).
- **GitHub Discussions** — open-ended design questions, show-and-tell, integration recipes: [discussions](https://github.com/vavallee/bindery/discussions).

Expand Down
27 changes: 27 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ polls it after **Refresh metadata** to show the result once the sync is done,
and `POST /api/v1/author/{id}/refresh` answers **409 Conflict** while it is set
instead of starting a second sync (#2601).

`POST /api/v1/author` accepts an optional `monitorNewItems` (#2541), the same
field `PUT /api/v1/author/{id}` takes: `all` (the default) lets a refresh add
newly discovered books, `none` keeps the catalogue as it was added. Any other
value is rejected with 400.

`POST /api/v1/author` responses include a `providerMismatch` object when the
linked record routes its catalogue syncs to a provider other than the
configured `metadata.primary_provider` (#2237), so a fallback pick is visible
Expand Down Expand Up @@ -184,6 +189,20 @@ POST /api/v1/book/{id}/search manual indexer search
GET /api/v1/book/{id}/file download the imported file (auth required; `?path=…` serves one specific tracked file, for a book holding several of a format; `?format=ebook|audiobook` picks the format on dual-format books; `?path=` wins when both are sent)
```

### Series

```
GET /api/v1/series list series with their linked books
GET /api/v1/series/{id} one series
POST /api/v1/series/{id}/fill add the series' missing books as wanted (admin)
PATCH /api/v1/series/{id} monitor / unmonitor (admin)
```

`GET /series` returns the bare array it always has. Pagination is opt-in
(#2345): pass `limit` and/or `offset` and you get an `{items, total, limit,
offset}` envelope instead, which is what a large catalogue wants, since the
unpaged response carries every series with every linked book.

### Search & discovery

```
Expand Down Expand Up @@ -240,6 +259,14 @@ value is rejected with 400. `GET /indexer` and `GET /indexer/{id}` also return
`dailyQueriesUsed` on capped indexers, which is a display figure summed from the
stored hourly buckets and lags the live tally by up to one flush interval.

#### Rate limit holds

When Bindery is holding off on an indexer after a rate limit (#2640),
`GET /indexer` and `GET /indexer/{id}` carry `cooldownUntil` (a timestamp) and
`cooldownReason`. Both are response-only, are omitted when nothing is held, and
are ignored if a client sends them. The hold lives in memory, so it is gone
after a restart.

#### Indexer and Prowlarr API keys are write-only

Indexer and Prowlarr responses never carry the stored credential. Every
Expand Down
2 changes: 1 addition & 1 deletion docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ Set a download-client path remap in **Settings → Download clients** or set the

Per-client remaps are stored on each download client, so separate qBittorrent / SABnzbd / NZBGet instances can map different mount points. Existing download clients keep an empty remap after upgrade, which preserves the previous global-only behavior until you add a client-specific value.

For a per-client remap, open **Settings → Download clients**, edit the client, and set **Download client path remap**. The left side is the path the client reports; the right side is the path Bindery can read. For qBittorrent this normally means mapping the qBittorrent category save path or torrent content path to Bindery's download mount. Example: if qBittorrent reports `/downloads/books/My.Book` and Bindery sees that same folder as `/media/books/My.Book`, set `/downloads:/media/books`.
For a per-client remap, open **Settings → Download clients**, edit the client, and set **Download client path remap**. To see what a remap actually resolves to, press **Diagnose** on the client: it reports where that client says ebook and audiobook grabs land, which remap applies, and whether Bindery can read the result. The left side is the path the client reports; the right side is the path Bindery can read. For qBittorrent this normally means mapping the qBittorrent category save path or torrent content path to Bindery's download mount. Example: if qBittorrent reports `/downloads/books/My.Book` and Bindery sees that same folder as `/media/books/My.Book`, set `/downloads:/media/books`.

**Common scenario — SABnzbd or qBittorrent and Bindery on the same NAS storage, different mount points:**

Expand Down
2 changes: 1 addition & 1 deletion docs/Hardcover-Series-Wiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ If either requirement is missing, Bindery hides the enhanced controls and the en

Use **Search series...** at the top of the Series page to filter your local series by title instead of using browser Find in a large collection. Matching ignores case and accents; clear the field to show all series again. This local search is available without enhanced Hardcover data.

1. Open **Series**.
1. Open **Library → Series**.
2. Create a series manually, or open an existing series populated from your library metadata.
3. Use **Add Book** to attach existing Bindery books and set their series positions.
4. Use **Search** to find a matching Hardcover series.
Expand Down
2 changes: 1 addition & 1 deletion docs/Migrating-From-Readarr-Wiki.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Bindery is a single instance. One author record covers ebook, audiobook, or both

## Bringing in books already on disk

For files already on disk, use **Library Scan**. It takes the author and title from your folder layout: a file under `{Author}/{Book}/` — Readarr's and Calibre's default structure — is matched on the folder names, so the filename convention (`Author - Title` vs `Title - Author`) does not matter. Loose files with no author/book folders fall back to filename parsing, which can still be ambiguous, so keep an organised folder structure for the most reliable scan. **Bulk folder import** reads the same author folders when you point it at the folder that holds them.
For files already on disk, use **Library Scan**. It takes the author from your folder layout: a file under `{Author}/{Book}/` — Readarr's and Calibre's default structure — is matched on the author folder, so the filename convention (`Author - Title` vs `Title - Author`) does not matter for the author. An ebook's title is read from the file's own name first and from the book folder only when the filename matches nothing, so a `Series/01 - Title.epub` layout matches the book the file names. An audiobook's title comes from its folder, where the files are its tracks. Loose files with no author/book folders fall back to filename parsing, which can still be ambiguous, so keep an organised folder structure for the most reliable scan. **Bulk folder import** reads the same author folders when you point it at the folder that holds them.

One difference to know about before you start correcting matches: Readarr's fix-match only changes which record a file is linked to, while Bindery's **Fix match** re-runs the import, so it moves the file into the target book's folder and renames it from your naming template. The confirmation step names the destination path before anything happens, so you can back out if you would rather keep your existing layout. Reassigning without relocating is not available yet (#2055).

Expand Down
2 changes: 1 addition & 1 deletion docs/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Use **Test** to confirm the connection.

## 5. Add an author and grab a book

Go to **Authors → Add Author**. Type a name and press Enter (results come from
Go to **Library → Authors** and press **Add Author**. Type a name and press Enter (results come from
OpenLibrary / Hardcover / DNB); the list shows the author followed by their
books. Select the author row, choose a **Monitor mode** (default monitors all
books), and add them. Adding an author **populates their catalogue** —
Expand Down
Loading
Loading