BookshelfNG is a self-hosted ebook and audiobook library manager descended from Readarr. Follow authors and books, monitor indexers for new releases, automatically grab downloads, and organize, rename, and upgrade files in your library. BookshelfNG is a complete app on its own. It does not require SeerrNG or a metadata proxy.
BookshelfNG is built on .NET 10 LTS.
Support BookshelfNG on Ko-fi.
Published containers and native release archives include the required runtime; source builds require the .NET 10 SDK.
On Unraid, install BookshelfNG from Community Apps.
See the Unraid installation guide
for persistent storage, download paths, and Hardcover setup. The package uses
ghcr.io/snapetech/bookshelfng:latest, which tracks the Hardcover image; the
upstream Bookshelf listing uses a different image. SeerrNG integration is optional.
BookshelfNG is for people who want Readarr-style book automation with a choice of metadata providers. Goodreads-compatible metadata services and native Hardcover remain available for primary library refreshes. Runtime catalog search is independently selectable across those providers, Open Library, and the other public catalogs. See Metadata sources for provider behavior and settings.
A single BookshelfNG instance and database can track ebooks and audiobooks on the same author and book records. You do not need separate BookshelfNG instances just to support both formats. When SeerrNG routes ebook and audiobook requests separately, both service entries can point to this same BookshelfNG URL and API key.
Each author keeps the existing Path as the default, with optional ebook and
audiobook folder overrides for future imports, upgrades, and renames. Changing
an override alone does not move existing files. In the author editor, use
Move existing ebooks or Move existing audiobooks to preview and queue a
move for one author. To move one format for several authors, open the author
library, choose Author Editor, select authors, then choose Move selected
media. Both workflows are also available through the
API and CLI.
A move includes only media files and book sidecars registered in the current instance. It does not sweep unknown files, move author-wide extras, or merge a second instance's catalog or database. Bulk previews show each destination, missing files, conflicts, and estimated copy space; any conflict prevents the batch from being queued. A queued move runs in the background and is not an all-or-nothing transaction, so review its progress in Activity → Queue. Move actions require the instance's administrator API key. BookshelfNG has no separate in-app administrator and regular-user roles.
For a legacy ebook/audiobook setup with two BookshelfNG instances, move each instance's tracked format from that source instance, then import or rescan the files in the instance you are keeping. Keep both source databases until the files have been moved and verified: BookshelfNG does not merge their author, book, profile, monitoring, or history records. The full legacy consolidation runbook covers destination mounts, backups, the UI/API/CLI steps, and follow-up import. Quality and metadata profiles remain shared by both formats in each instance; separate instances remain an option for isolated databases or settings. Per-format profiles are on the parity roadmap.
- Choose how book metadata is served. The
hardcoverimage includes a native Hardcover GraphQL provider. Search, author, work, edition, series, ISBN, and ASIN lookups go from BookshelfNG directly to Hardcover. There is no proxy between them. When you need one, BookshelfNG can use a Readarr-compatible metadata endpoint instead, including a service you host yourself. - Keep old libraries usable. The
softcoverimage retains Goodreads compatible IDs and import behavior for existing Readarr/softcover databases. You can also choose a compatible metadata endpoint withMETADATA_URL. - Identify ebooks from their own metadata. BookshelfNG reads identifiers embedded in ebook files, strips ISBN formatting, validates ISBN-10 and ISBN-13 check digits, and prefers ISBN-13 when an ebook provides multiple valid identifiers. ISBN and ASIN are also used for direct metadata searches and edition matching.
- Control catalog search timing. The Add page waits for you to submit a search by default. Enable search-as-you-type when you want results as you enter a query; the preference is saved in the current browser.
- Find and compare likely releases. Import identification searches by ISBN, ASIN, and Goodreads ID before using author and title. If author information is missing, it can search by title alone. Candidate editions are ranked using identifiers and available edition details; ambiguous matches can still need review. Search result ordering is preserved.
- Keep multiple formats for a book. Different ebook formats and audiobook files can stay attached to the same book. Import and upgrade decisions only compare compatible formats; see import matching and format support.
- Import common ebook and audiobook formats. Recognized ebooks include EPUB, KEPUB, MOBI, AZW3, and PDF. Audiobooks include M4B, MP3, FLAC, AAC, M4A, OGG, and related formats; see the complete extension list.
- Tune edition matching and series names. Metadata profiles can rank edition title, format, and publisher terms, and choose preferred series for filenames and metadata. See import matching and format support.
- Import books from the lists you use. BookshelfNG supports Goodreads shelves, owned books, series, and Listopia lists, plus native Hardcover list imports. Hardcover imports honor the lists selected in configuration.
- Search MyAnonamouse directly. The native MyAnonamouse indexer lets you use the tracker without adding Prowlarr as an intermediary.
- Connect current download clients. BookshelfNG supports qBittorrent 5.2 authentication alongside the download clients inherited from Readarr.
- Build chaptered audiobook files when needed. Completed downloads with
multiple tracks for one edition can be merged into a single M4B when
BOOKSHELF_M4B_MERGE=trueand an FFmpeg executable is available. This is opt-in; the standard Docker image includes FFmpeg. - Keep the familiar automation. Monitor authors and books, watch indexer RSS feeds for releases, search for missing monitored books, apply quality and metadata profiles, manage download clients, scan and import existing files, and rename or upgrade releases automatically. Optionally move configured extra files when author renaming moves books to a new folder; see sidecar rename behavior.
- Search and recover downloads. Use interactive release search to choose an available release when an indexer supports it. BookshelfNG can search again after a failed download; automatic grabs and manually selected grabs have separate re-search settings. You can adopt a matching torrent already in a download client, and ignore an unmatched queue item without deleting the download. For supported ebook sources, add the built-in Direct Download indexer. It uses an API key when configured and can optionally resolve slow links in headless Chromium; its built-in client tracks the transfer without another download-client service. Direct search is interactive and does not provide RSS. See search and download workflows.
- Use narrator credits to identify audiobooks. Import matching compares narrator tags with edition metadata when both are present. Narrators stay separate from authors, appear in book search and filtering, and can be written to audio files when audio tag writing is enabled. See audiobook metadata.
- Send imports to BookLore. Add BookLore as a notification destination to upload imported files to its BookDrop review queue. See the BookLore integration guide.
- Connect a Calibre Content Server. Configure a Calibre library as a root folder to add imported books to the library, sync metadata, and convert into configured output formats.
- Keep library activity private. BookshelfNG removes Servarr's Sentry analytics and exception-reporting integration. An independent diagnostics module is available for operators who explicitly install and enable it; the standard app image contains no reporting module or telemetry SDK.
BookshelfNG retains the Readarr-compatible API and the broader Readarr library-management workflow. A book can keep multiple ebook formats and audio files in one instance; import and upgrade checks keep those formats separate. See import matching and format support.
Some capabilities are part of the Bookshelf and Readarr lineage rather than unique BookshelfNG additions. The capabilities and compatibility guide compares those upstream features with BookshelfNG's maintained changes and documents provider boundaries, migration limits, and implementation links.
Author and book metadata are persisted in BookshelfNG's application database. Refreshing a book for an author already in the library reuses that local author record when the provider IDs match, so a book refresh does not need to fetch the author's full catalog again. Automatic author validation is age and activity based; manual refreshes and metadata-profile changes can force an update. See Author metadata storage and refresh behavior for the exact rules, Hardcover request caching and rate-limit behavior, failure handling, troubleshooting logs, and source-code references.
For a normal Docker install, use the published image below. You do not need to
clone the source repository or run build.sh; the image already contains the
backend and web UI. Save the example as compose.yaml in an empty directory.
The web interface listens on port 8787. Persist /config, and mount your
downloads and library paths so BookshelfNG and your download clients can
access them.
services:
bookshelf:
image: ghcr.io/snapetech/bookshelfng:hardcover
ports:
- "127.0.0.1:8787:8787"
volumes:
- ./bookshelf-config:/config
- /path/to/downloads:/downloads
- /path/to/books:/books
restart: unless-stoppedRun docker compose pull and docker compose up -d, then open
http://localhost:8787 to set up your root folder, metadata source, indexers,
and download clients. The
softcover image is available for Goodreads-compatible libraries. Both
published Linux images support amd64 and arm64 hosts.
BookshelfNG also runs directly on Windows, macOS, Linux, and FreeBSD. Native release archives include the .NET runtime and web UI; they do not require SeerrNG or a container runtime. Start an extracted Linux, macOS, or FreeBSD archive with:
./Readarr -nobrowser -data="$HOME/.local/share/bookshelfng"On Windows, run Readarr.exe and set a persistent data path under
%LOCALAPPDATA%, or use the Windows x64 setup installer attached to each
release. The installer registers BookshelfNG as a service and backs up the
existing Readarr-compatible data before upgrades. Then open
http://localhost:8787. The
standalone installation guide covers platform
archives, data migration, system services, and package-specific setup.
The release pipeline builds .deb, RPM, Flatpak bundle, AppImage, and Helm
artifacts, and supports AUR, COPR, Ubuntu PPA, and Chocolatey publishing when
those publisher accounts are connected. See the
distribution channels guide for package links,
repository setup, and release details.
To combine a multi-track audiobook download into a single chaptered M4B, set
BOOKSHELF_M4B_MERGE=true. This applies to completed downloads received from a
download client. BookshelfNG groups approved tracks by book and edition,
orders them using embedded disc and track numbers or filenames, creates a
chapter for each track, and reruns the normal import checks on the merged
file. If FFmpeg is unavailable, a merge fails, or the merged file fails import
checks, BookshelfNG keeps the original tracks and continues the normal import.
The standard Docker image includes FFmpeg. For native installs, provide an
executable named ffmpeg on BookshelfNG's PATH, or set
BOOKSHELF_FFMPEG_PATH to its path. The merge transcodes audio to stereo AAC
at 44.1 kHz and 128 kbps by default.
Set BOOKSHELF_M4B_AAC_BITRATE_KBPS to a value from 48 to 320 to choose a
different bitrate. When the merged file imports successfully in move mode,
the original tracks are removed; copy mode and downloads that cannot be moved
retain their source tracks. See the capabilities and compatibility guide
for the exact behavior and implementation references.
The hardcover image uses BookshelfNG's native Hardcover GraphQL provider by
default. It searches Hardcover for books and authors and maps work, edition,
series, ISBN, and ASIN data into Bookshelf's existing metadata model. This
removes the extra metadata service from the normal request path.
Native access requires a Hardcover API token at runtime:
HARDCOVER=true
HARDCOVER_AUTH=Bearer your-hardcover-api-tokenHARDCOVER_API_KEY is accepted in place of HARDCOVER_AUTH. The default API
endpoint is https://api.hardcover.app; set HARDCOVER_API_URL to use a
compatible endpoint or test service. The Hardcover token can also be entered
in Settings > Metadata > Primary Metadata Provider. Environment credentials
take precedence over the saved setting; the settings API never returns the
saved token.
Native mode handles transient request failures and sparse or nullable metadata responses, maps Hardcover work and edition records into Bookshelf's library model, and caches responses in the BookshelfNG process for the lifetime of the instance. Author responses are cached for 30 minutes, work responses for six hours, and search responses for 10 minutes. These response caches are separate from the author and book metadata stored in the library database. See the Hardcover API getting-started guide and Hardcover author schema for Hardcover's API reference. BookshelfNG does not silently switch to another metadata service after an API error.
When BookshelfNG creates the initial Standard metadata profile on a database
with no profiles, HARDCOVER=true selects a minimum popularity of 50; other
modes use 350. This lower default can keep more low-popularity catalog books
during refresh. Existing profiles are preserved and can be adjusted in the
metadata profile settings.
Text search fetches Hardcover's result IDs in one batched book query instead
of making a separate detail request for every result. ISBN/ASIN searches and
edition lookups fetch the parent work in the same GraphQL operation. API
requests are paced at one per second. BookshelfNG honors Hardcover's
Retry-After and exhausted rate-limit headers; when a limit is reached, it
stops sending further requests from this process until the reported reset and
returns an error for calls made during that cooldown instead of retrying into
the limit.
Every provider used for runtime book discovery can be turned on or off independently in Settings > Metadata > Runtime Catalog Sources. This selection controls title, ISBN, ASIN, and import searches. It does not change how existing library records refresh. Results from an alternate catalog retain a provider-qualified ID and continue to resolve through that provider. Results from the configured primary catalog keep its existing ID format so current libraries continue to match them; older unqualified IDs also keep using the configured primary provider.
The selectable sources are Hardcover, the configured Readarr-compatible metadata API, Open Library, Google Books, Library of Congress, Gutendex, Internet Archive, NDL Search, Europeana, and the Goodreads-compatible Apify adapter. With no saved source selection, Bookshelf enables LOC, Gutendex, and the currently configured primary provider. Open Library, Internet Archive, NDL Search, and Apify are opt-in. Google Books and Europeana are enabled when their keys are set. Hardcover needs its API token. All sources can be disabled, including the configured primary provider's catalog search.
Field-level metadata preferences are configured separately from catalog search. Choose an enabled supplemental catalog for a book's title, description, publisher, language, release date, page count, cover, genres, or author image. Edition details require a matching ISBN; work-level metadata and images can also use an exact title-and-author match. See capabilities and compatibility. Use catalog metadata composition and unmapped-file recovery to retry old no-match files in the UI, API, or CLI.
BOOKSHELF_METADATA_SOURCES accepts hardcover, metadata-api,
openlibrary, googlebooks, loc, gutendex, internetarchive, ndl,
europeana, and apify-goodreads. A custom value replaces the saved UI
selection; an empty value disables all runtime catalog searches. The
environment setting remains authoritative and disables catalog selection in
the UI. Saved credentials are stored in the BookshelfNG configuration database
and returned as presence flags only. Non-empty credential environment values
take precedence over saved values.
HARDCOVER=true
HARDCOVER_AUTH=Bearer your-hardcover-api-token
GOOGLE_BOOKS_API_KEY=your-google-books-api-key
EUROPEANA_API_KEY=your-europeana-api-keyWith these keys, Google Books and Europeana results are also queried alongside
the other selected sources. Library of Congress and Gutendex run without keys. Gutendex covers
Project Gutenberg's literature catalog, including multiple languages, and is
not a current commercial-book catalog. Google Books and Europeana are skipped
when their keys are absent. To keep only the default public catalogs, set
BOOKSHELF_METADATA_SOURCES=loc,gutendex,openlibrary; to disable all catalog
searches, set
BOOKSHELF_METADATA_SOURCES=.
Open Library can be enabled in Settings > Metadata > Runtime Catalog
Sources. It uses its public Search, Work, Edition, and Author APIs. Requests
identify BookshelfNG with its User-Agent, are paced to one per second, and are
cached for one day. Add a contact email in the same settings page or set
OPEN_LIBRARY_CONTACT_EMAIL so Open Library can identify the installation.
Open Library Work IDs and Author IDs stay namespaced in BookshelfNG, and its
edition mapping validates and normalizes ISBN-10 values to ISBN-13. Covers are
loaded from Open Library's Covers API when available. Read Open Library's
API usage policy,
Search API, and
Covers API documentation.
Internet Archive searches its text collection and resolves records by stable item identifier. Catalog records and edition metadata vary, so treat it as an optional fallback. NDL Search adds Japanese and partner-library metadata; it does not provide artwork because NDL ended its thumbnail service on March 31, 2026. It is opt-in because requests are paced to one per second and usage conditions vary by provider. Operators must show NDL Search API credit and check the provider's licensing/application requirements, especially for commercial or continuous use. See NDL's thumbnail service notice.
To add a Goodreads-compatible Apify Actor:
# Include only the providers you want; use metadata-api instead of hardcover
# when the Readarr-compatible metadata API should be searched.
BOOKSHELF_METADATA_SOURCES=hardcover,googlebooks,loc,gutendex,internetarchive,ndl,europeana,apify-goodreads
HARDCOVER_APIFY_GOODREADS_ACTOR=publisher~goodreads-scraper
HARDCOVER_APIFY_TOKEN=your-apify-token
# Override only when the Actor does not use searchQueries and maxItems:
HARDCOVER_APIFY_GOODREADS_INPUT_TEMPLATE={"searchQueries":[{{query}}],"maxItems":10}Google Books public searches require a Google API key; user OAuth is not
needed. Europeana requires a free key from a registered account and searches
text records with open reuse status. It is a cultural heritage index rather
than a complete current-book catalog, and per-record metadata and cover
availability vary. See Europeana's Search API documentation,
Record API documentation,
and API key registration. Library of
Congress search is public and rate limited. Requests are paced to one per 3.2
seconds per Bookshelf process and successful responses are cached for one day.
If multiple Bookshelf processes share an outbound IP, set a source list that
avoids querying LOC from every process. The optional Apify adapter runs a
user-selected Actor, whose schema, availability, terms, and pricing are
controlled by its publisher. The default Actor input is
{"searchQueries":[{{query}}],"maxItems":10}; a custom JSON template must
include the literal {{query}} placeholder, which Bookshelf replaces with a
JSON-escaped search string. Actor output is normalized from common Goodreads
scraper fields, and results missing a title or author are skipped.
Results from alternate providers receive a namespaced foreign ID such as
hardcover:123, metadata-api:123, openlibrary:OL...W,
googlebooks:volume-id, loc:<encoded-record-url>,
europeana:<encoded-record-id>, or apify-goodreads:<encoded-record-key>.
Bookshelf uses those same IDs for subsequent book and author metadata lookups;
it does not coerce them into numeric Goodreads IDs. The selected provider
names and credentials must remain
available when the library is refreshed. Search results are cached for 10
minutes; Open Library, Google Books, LOC, and Europeana responses and Apify
result sets are cached for one day. Provider failures are logged independently;
other configured sources continue to return results.
The source checkboxes and optional credentials are managed separately by each
BookshelfNG instance. If you choose to run separate ebook and audiobook
instances, configure each in its own Settings > Metadata page.
HARDCOVER_NATIVE,
METADATA_URL, and HARDCOVER_API_URL still select deployment mode or an API
endpoint and remain environment-level settings. SeerrNG's Settings >
Metadata page explains this boundary; its TMDB/TVDB selectors control video
metadata, not BookshelfNG catalogs. One-off SeerrNG migration-recovery adapters
also keep their own environment settings because they run outside BookshelfNG's
normal search process.
The source setting applies to the hardcover and softcover images. It does
not rewrite IDs already stored in the library, and it does not combine remote
catalog records into Hardcover itself.
The softcover image is intended for Goodreads-compatible metadata and
existing Readarr/softcover databases. Goodreads and Hardcover use different
foreign author, book, and edition IDs; those IDs cannot be converted by
changing an image tag or metadata URL.
BookshelfNG also supports the Readarr-compatible metadata API through
METADATA_URL. Use a hosted service such as rreading-glasses, preserve an
existing proxy or cache, or run your own compatible endpoint. In the
hardcover image, set HARDCOVER_NATIVE=false to select this compatibility
path instead of direct Hardcover GraphQL access. The choice is explicit, so
you can see which service is handling metadata requests.
METADATA_URL is a compatibility boundary, not an automatic multi-provider
aggregator. It can point to a service that itself translates or combines
providers, but BookshelfNG expects that service to return stable IDs and
compatible detail responses. Goodreads work and edition IDs, Hardcover IDs,
Google Books volume IDs, Open Library keys, and LOC identifiers are not
interchangeable. The optional runtime catalogs documented above provide
parallel search results and provider-specific detail lookups; they do not
rewrite IDs already stored in the library or silently replace a failed primary
provider.
Do not switch an existing Goodreads/softcover database to Hardcover by changing
only the image tag or METADATA_URL. The two providers' foreign IDs are not
portable, and a direct switch can leave existing authors, books, and editions
that Hardcover cannot resolve.
Back up the ebook and audiobook configuration directories before migrating.
SeerrNG includes a migration helper that can inventory the source database,
rebuild strict matches against a temporary Hardcover BookshelfNG target, and
optionally use a softcover endpoint to recover metadata for stale IDs. Its
deterministic local database fallback can preserve books that Hardcover still
cannot import. Those fallback entries, such as local:ebook:1076, are local
Bookshelf records rather than native Hardcover records.
SeerrNG's migration helper can additionally search Google Books and the Library of Congress and can optionally call a user-selected Apify Actor that scrapes Goodreads-compatible catalogs. These sources are used to recover candidate metadata and attempt strict remapping to Hardcover. Unmatched metadata can enrich the optional local fallback. This tooling does not add Google Books, LOC, or Apify results to BookshelfNG's ordinary search/detail flows. Apify Actors are third-party services with Actor-specific schemas and potential charges; Goodreads does not issue new public developer API keys.
SeerrNG can connect to BookshelfNG as a Readarr-compatible ebook or audiobook service. That integration is optional; BookshelfNG can monitor, download, and manage a library without SeerrNG.
Use one BookshelfNG instance and its existing database for both SeerrNG service
entries. New installations require Forms authentication for the web interface;
SeerrNG should use an API key in the X-Api-Key header. Legacy Readarr clients
that send ?apikey= remain supported; set BOOKSHELF_ALLOW_API_KEY_QUERY=false
to require header credentials. To give SeerrNG a
separate restricted key, set BOOKSHELF_SEERRNG_API_KEY from a Docker/Kubernetes
secret. That credential can read library/search/profile/root-folder data and
add or update authors and books, but it cannot change system settings, reset
the administrator key, or run arbitrary commands. Replace the secret and
restart BookshelfNG to rotate it. Existing global API keys continue to work.
BookshelfNG advertises a versioned SeerrNG capability contract and accepts the
format-scoped routes /readarr/{gr|hc}/ebook/api/v1 and
/readarr/{gr|hc}/audiobook/api/v1, where the dialect matches the configured
metadata provider. Both routes use the same BookshelfNG library and
database; a second instance or a database merge is not required. See the
SeerrNG integration and security guide for
trusted proxy, allowed-origin, API credential, and upgrade details.
For compatible book-add requests, BookshelfNG's
/api/v1/book/lookup response includes nested author and edition metadata when
it can resolve the requested book. Use the softcover image when retaining a
Readarr-compatible Goodreads database, or use the Hardcover migration process
above when moving to Hardcover metadata.
GitHub Actions publishes these rolling and versioned container tags:
softcoverandsoftcover-v<release-version>for the Readarr-compatible metadata modehardcover,latest, andhardcover-v<release-version>for native Hardcover mode
Tagged main-v* builds also publish a GitHub Release with curated release
notes, platform archives, the Windows x64 setup installer, checksums, and native
Linux packages, then announce the release to Discord. The AUR, COPR, PPA,
Chocolatey, and Helm channels use the same verified tag flow. External
stores require publisher credentials and repository registration. The Flatpak is
attached as a GitHub Release bundle and is not published to Flathub. See the
distribution setup and publisher requirements for
channel details. Pull requests require a release-note fragment for user-facing
changes; internal-only work must be marked release-note: none. See
release-notes/README.md for the format and
preview command.
If an anonymous pull returns denied, the GHCR package may not be public yet;
you can also authenticate with package read access.
- Documentation index
- Fork feature inventory, upstream comparison, and compatibility boundaries
- Import matching and multiple formats
- Search, direct downloads, torrent adoption, and queue workflows
- Audiobook narrator tags and Audiobookshelf sidecars
- BookLore import uploads
- Series pack search
- Optional audiobook M4B merging
- Metadata providers and configuration
- Moving an existing library to Hardcover
- Author metadata storage, refresh policy, and troubleshooting
- Reporting issues and collecting useful logs
- .NET 10 source builds, including the UI and Linux x86/FreeBSD packages
- Standalone installation and platform support
- Release distribution channels and publisher setup
- Optional diagnostics module
- Release-note format and preview
- SeerrNG migration metadata source matrix
Please file a GitHub issue or start a discussion for help. Contributions are welcome, especially fixes and quality-of-life improvements.
After cloning, maintainers must run scripts/install-yunohost-sync-hook.sh to
install the post-commit and pre-push package sync hooks. Commits sync the
YunoHost and Unraid package repositories immediately. Before a BookshelfNG
push, both syncs run again and a failure blocks that push. GitHub protects
main with a required pull request and package sync check, so a local hook
bypass cannot merge package changes that are missing from either repository.
Release workflows run the same check before publishing. Missing package
sources or companion checkouts are errors. Set BOOKSHELFNG_UNRAID_REPO or
BOOKSHELFNG_YNH_REPO if a companion checkout is outside the sibling-repository
layout.
The standard BookshelfNG image sends no analytics or exception reports. An optional module can report aggregated application-start, book-grab, book-import, and download-failure counts to an OTLP collector you configure. It requires the module assembly, an explicit enable flag, an HTTPS endpoint, and a bearer token. It sends no book or author metadata, paths, search terms, log messages, exception details, or persistent installation identifier. The collector can still see the source IP address of the HTTPS request. See the diagnostics module instructions for the data schema and installation steps.
BookshelfNG is derived from Bookshelf, a revival of Readarr, and Prowlarr. Those projects are licensed under GPLv3, and BookshelfNG is distributed under the terms of GPLv3. See LICENSE.
Copyright 2025–2026.