Sign inSign up

jncchds/nopds

By jncchds

•Updated 6 days ago

Self-hosted e-book library server with OPDS, web reader and Telegram bot (.NET)

Image
0

620

jncchds/nopds repository overview

Source code, issues and docs: jncchds/nopds⁠

⁠.NET OPDS by CHDS

A self-hosted e-book library server: scan your book folders, browse and read them in a modern web app, and serve them to e-readers over OPDS.

Built with ASP.NET Core (.NET 10), PostgreSQL and a React + TypeScript single-page app, served together from one process.

Inspired by SimpleOPDS (sopds)⁠ by Dmitry Shelepnev. The OPDS URL layout is kept compatible, so existing reader bookmarks keep working.

⁠Features

Library

  • Multiple libraries, each with its own folder, schedule and scan options
  • FB2, EPUB, MOBI/AZW3, PDF, DJVU, CBZ, TXT, RTF, DOC/DOCX
  • Books inside ZIP archives, and INPX indexes (MyHomeLib/LibRusEc collections)
  • Incremental scanning: unchanged files and archives are skipped (size + mtime); parsing runs in parallel with a batched single writer
  • Cron schedule per library, optional folder watching (partial rescans of changed folders), live progress in the admin UI
  • Smart duplicates: editions of the same work (normalized title + authors) collapse into one entry, with a preferred format order; optional content hashing for exact duplicates
  • Soft or hard delete of books whose files disappear, with automatic restore
  • Optional upload library: signed-in users upload books from the web app; uploads are public by default or private to the uploader (and admins) — also in OPDS and the Telegram bot
  • Covers extracted on demand and cached as WebP thumbnails

Reading & downloads

  • In-browser reader (foliate-js) for EPUB, FB2, MOBI/AZW3 and CBZ, plus DOCX, ODT, RTF, TXT and HTML through on-the-fly EPUB conversion, with saved reading position
  • Built-in EPUB 3 converters for FB2, DOCX, ODT, RTF, TXT and HTML (no Java or Python tools), plus configurable external converters (e.g. ebook-convert, kepubify) that chain with them (DOCX → EPUB → AZW3), with an LRU cache
  • Metadata (title, authors, language) read from DOCX, ODT, RTF and HTML files
  • Downloads as original, zipped or converted, with real UTF-8 file names

Clients

  • OPDS 1.2 (Atom) and OPDS 2.0 (JSON) catalogs: by folders, titles, authors, series, genres, new books, bookshelf, search (OpenSearch), duplicate facets
  • Readers authenticate with HTTP Basic or a personal feed link (/opds/t/<token>/) for apps without login support
  • KOReader progress sync (kosync-compatible)
  • Optional Telegram bot: search, book cards, file delivery; users link their Telegram account from Settings with a one-time deep link (QR code for linking from a phone), no Telegram user name needed

Web app

  • Browse by alphabet (Cyrillic/Latin/digits), authors, series, genres, folders; full search
  • Bookshelf with "continue reading"
  • UI in Ukrainian, English, Polish, German, following the system language by default (Russian and Belarusian fall back to Ukrainian, others to English)
  • Light and dark themes, installable PWA with offline covers and recently opened books
  • Admin: libraries (with a server folder picker), scans, settings, users with per-library access, logs

Security

  • JWT access tokens (short-lived, in memory) plus rotating refresh tokens in an httpOnly cookie, with reuse detection
  • ASP.NET Identity password hashing, lockout, rate-limited login
  • Optional single sign-on with Authentik (or any OpenID Connect provider): accounts are created on first sign-in and, by default, wait for admin approval

⁠Quick start (Docker)

git clone https://github.com/jncchds/nopds.git
cd nopds
NOPDS_BOOKS=/path/to/your/books NOPDS_ADMIN_PASSWORD=choose-a-password docker compose up -d --build

Open http://localhost:8080⁠, sign in as admin, go to Administration → Libraries → Add library and choose /books (the folder mounted from NOPDS_BOOKS). Start a scan.

The compose file runs two containers:

ServicePurpose
nopdsWeb app, API, OPDS, scanner, bot. Data (keys, logs, cache) in the nopds-data volume
postgresPostgreSQL 17, data in the nopds-db volume

Mount more folders (read-only) in docker-compose.yml to add more libraries.

⁠Connecting readers

ClientURL
OPDS 1.2 with loginhttp://<host>:8080/opds/
OPDS 1.2 personal linkhttp://<host>:8080/opds/t/<token>/ (see Settings, also as QR code)
OPDS 2.0http://<host>:8080/opds/v2/ or …/opds/t/<token>/v2/
One library onlyadd l/<libraryId>/, e.g. /opds/l/2/
KOReader progress syncCustom sync server http://<host>:8080/kosync, your user name and the sync password set in Settings

Any OPDS 1.2 client should work (KOReader, Moon+ Reader, FBReader, Librera, …); OPDS 2.0 is supported by newer apps such as Thorium and Readest.

⁠Configuration

Host settings come from appsettings.json or environment variables (__ separates sections):

SettingDefaultDescription
ConnectionStrings__Nopdslocal PostgreSQLNpgsql connection string
Nopds__DataDirdataSigning keys, data-protection keys, logs
Nopds__CacheDirdata/cacheCovers, thumbnails, converted books
Nopds__AdminUser / Nopds__AdminPasswordadmin / –First admin, created only when no users exist
Nopds__AdminForcefalseRecovery: on every start, create AdminUser if missing, reset its password to AdminPassword, restore admin rights, approve and unlock it
Nopds__AutoMigratetrueApply database migrations on start
Nopds__Jwt__KeygeneratedHMAC key (≥ 32 bytes); generated into DataDir when empty
Nopds__Jwt__AccessTokenMinutes15Access token lifetime
Nopds__Jwt__RefreshTokenDays30Refresh token lifetime
Nopds__Oidc__Authority–OpenID Connect issuer URL; single sign-on is off when empty
Nopds__Oidc__ClientId / Nopds__Oidc__ClientSecret–OAuth2 client credentials
Nopds__Oidc__DisplayNameAuthentikProvider name on the login button
Nopds__Oidc__Scopes__0…openid profile emailRequested scopes
Nopds__UploadPath–Folder of the upload library; uploading is off when empty (see below)
Nopds__UploadMaxMegabytes200Largest accepted upload (all files of one upload together)

Everything else (site title, public/private access, page sizes, duplicate handling, covers, converters, Telegram bot, SSO approval) is edited at runtime in Administration → Settings. Library options (extensions, ZIP code page, INPX, schedule, watching, soft delete, hashing) are per library.

Behind a reverse proxy, forward X-Forwarded-Proto and X-Forwarded-Host so OPDS links use the public address.

⁠Upload library

Set Nopds__UploadPath to let users add books themselves. On start a library for that folder is created (named Uploads) and scanned, tracked and browsed like any other; every signed-in user can open it and upload to it from the Upload page, whatever their per-library access.

  • Uploads are public by default. The uploader can mark a book private when uploading or later (upload page or book page); private books are shown only to the uploader and admins — in the web app, OPDS feeds, downloads and the Telegram bot. Admins can change the privacy of any upload.

  • Files placed in the folder by other means (copied in by hand, or there before uploads were enabled) are treated as public uploads of the first user (the oldest account, normally the main admin) on the next scan.

  • When a scan finds an uploaded file missing, the book is hidden, not removed, so its owner and privacy come back with the file.

  • The folder must be writable. In Docker, bind it to a host folder so books survive container rebuilds — uncomment both lines in docker-compose.yml:

    environment:
      Nopds__UploadPath: "/uploads"
    volumes:
      - ${NOPDS_UPLOADS:-./uploads}:/uploads
    

    The container runs as UID 1654, so give it the host folder: mkdir -p uploads && sudo chown 1654 uploads.

  • Removing Nopds__UploadPath turns uploading off; the library stays as a regular one and private books stay private.

⁠Single sign-on with Authentik
  1. In Authentik, create an OAuth2/OpenID Provider (confidential client, scopes openid, profile, email) with the redirect URI https://<your-nopds-host>/signin-oidc, and an Application using it (slug e.g. nopds).
  2. Configure NOPDS:
    Nopds__Oidc__Authority: "https://auth.example.com/application/o/nopds/"
    Nopds__Oidc__ClientId: "<client id>"
    Nopds__Oidc__ClientSecret: "<client secret>"
    
  3. The login page gets a Sign in with Authentik button. The first sign-in creates a local account (named after preferred_username; a number is appended if a local user already has that name — existing accounts are never taken over). By default the account waits until an admin approves it in Administration → Users, where admins also grant or revoke admin rights; turn this off in Administration → Settings → Single sign-on.

NOPDS must be served over HTTPS for the sign-in round trip (the OIDC correlation cookies are Secure). E-readers cannot use SSO: SSO users open their personal feed link from Settings, or set a local password there for HTTP Basic auth.

⁠Development

Requirements: .NET 10 SDK, Node.js 22, Docker.

docker compose -f docker-compose.dev.yml up -d        # PostgreSQL on localhost:5432
dotnet tool restore                                    # dotnet-ef
dotnet run --project src/Nopds.Web                     # API on http://localhost:5249 (admin / admin123 in Development)
cd src/Nopds.Web/ClientApp && npm install && npm run dev   # SPA on http://localhost:5173 (proxies to the API)

Useful commands:

dotnet test                                            # unit + integration tests (Testcontainers PostgreSQL)
cd src/Nopds.Web/ClientApp && npm run lint && npm run lint:i18n && npm run build
dotnet ef migrations add <Name> -p src/Nopds.Infrastructure -s src/Nopds.Infrastructure -o Data/Migrations
dotnet publish src/Nopds.Web -c Release                # also builds the SPA into wwwroot
⁠Project structure
src/Nopds.Domain          Entities and text helpers (language codes, transliteration, normalization)
src/Nopds.Infrastructure  EF Core/PostgreSQL, migrations, Identity user, settings, genre catalog, catalog queries
src/Nopds.Formats         FB2/EPUB/MOBI/CBZ parsers, book storage, covers, hashing
src/Nopds.Scanner         Incremental scanner, INPX reader, scheduler, folder watcher
src/Nopds.Conversion      EPUB 3 converters (FB2, DOCX, ODT, RTF, TXT, HTML), external converters, routing, cache
src/Nopds.Opds            OPDS 1.2 Atom and OPDS 2.0 JSON writers
src/Nopds.Telegram        Telegram bot
src/Nopds.Web             ASP.NET Core host: API, auth, OPDS/kosync endpoints, SignalR; ClientApp/ = React SPA
tests/Nopds.Tests         Parser, converter and integration tests
docs/                     Notes on the original SimpleOPDS architecture

⁠Acknowledgements

  • SimpleOPDS⁠ by Dmitry Shelepnev — the original project this one is modelled on (catalog structure, OPDS URL scheme, INPX handling, genre list)
  • foliate-js⁠ — in-browser e-book rendering
  • KOReader⁠ — the sync protocol

⁠License

GPL-3.0⁠. This project ports logic and data (language codes, transliteration, INPX parsing, the FB2 genre list) from SimpleOPDS, which is licensed under GPLv3.

Tag summary

Content type

Image

Digest

sha256:64c2b9f68…

Size

106.3 MB

Last updated

6 days ago

docker pull jncchds/nopds