Skip to content

Repository files navigation

Tsundoku

Self-hosted manga, manhwa, and light-novel reader for people who want their library on their own machine, their own storage, and their own terms.

Tsundoku is a single-container Go API and static SvelteKit application with a SQLite database. It reads local files and remote libraries through the same storage boundary, keeps reading progress per user, and exposes the library to web, desktop, Android, and OPDS clients.

Features

  • Manga and manhwa from CBZ archives and image directories.
  • Light novels from EPUB, FB2, and UTF-8 TXT files.
  • Local filesystem, S3-compatible, and WebDAV library backends.
  • Ranged remote reads for CBZ and EPUB archives without downloading a whole book.
  • Plain-text extraction for novels with bounded ZIP/XML parsing.
  • SQLite FTS5 full-text search with chapter and section results.
  • Authenticated reader, admin, and owner roles.
  • Server-side sessions, CSRF protection, secure cookies, and request rate limits.
  • Per-user reading progress and title subscriptions.
  • In-app notifications when subscribed titles receive new chapters.
  • OPDS 1.2 Atom catalog and publication downloads.
  • Open Library metadata import by ISBN without overwriting manual metadata.
  • Cover uploads, title metadata editing, tags, bulk operations, and scans.
  • Disk cache with a hard byte limit and optional background WebP conversion.
  • Tauri desktop application and CI-generated Android APKs.
  • OpenAPI contract with generated frontend types.
  • CGO-free production build for simple deployment and cross-compilation.

Quick Start

The Docker Compose setup starts Tsundoku on port 8080 and mounts a local library read-only.

docker compose -f deploy/docker-compose.yml up --build

Open http://localhost:8080/setup. The one-time setup token is printed in the container logs:

docker compose -f deploy/docker-compose.yml logs -f tsundoku

Create the owner account, then open the Libraries admin page and create a library pointing at /libraries/default.

The default host path is ./library. Use another path without changing the compose file:

TSUNDOKU_LIBRARY_PATH=/srv/books docker compose -f deploy/docker-compose.yml up --build

The compose file uses TSUNDOKU_SECURE_COOKIES=false for local HTTP. Set it to true when serving through HTTPS.

Supported Content

Comics

  • .cbz archives containing JPEG, PNG, or GIF pages.
  • Image directories containing JPEG, PNG, or GIF files.
  • Archive paths are validated against traversal, duplicate entries, size, decompression, image-dimension, and entry-count limits.

Light Novels

  • .epub files using the EPUB container, OPF manifest, and spine.
  • .fb2 FictionBook XML files.
  • UTF-8 .txt files.

Novel content is parsed into bounded plain-text sections before it is stored in the database. EPUB HTML/XHTML is not rendered directly, so scripts, styles, event handlers, and remote resources cannot execute in the reader.

The full-text index covers extracted novel text. It intentionally does not OCR comic images.

Storage Backends

Create and manage libraries from the owner Libraries page.

Local

Point the library at a directory visible to the server, for example /libraries/default in Docker.

S3-Compatible

Configure an endpoint if required, region, bucket, prefix, access key, and secret key. AWS S3 and S3-compatible services are supported. Custom endpoints must use HTTPS.

WebDAV

Configure an HTTPS base URL, username, and password. Cross-host and insecure redirects are rejected.

Remote secrets are encrypted with AES-256-GCM before they are stored. The encryption key is never returned by the API.

Configuration

Variable Default Description
TSUNDOKU_ADDR :8080 HTTP listen address.
TSUNDOKU_DATA_DIR ./data Database, covers, and cache root.
TSUNDOKU_FRONTEND_DIR ./frontend Built static frontend directory.
TSUNDOKU_CACHE_MAX_BYTES 5368709120 Maximum page/thumbnail cache size in bytes.
TSUNDOKU_SECURE_COOKIES false Set true when the application is served over HTTPS. Required for OPDS Basic Auth.
TSUNDOKU_TRUSTED_PROXIES empty Comma-separated proxy IPs or CIDRs used for client IP handling and rate limits.
TSUNDOKU_ENCRYPTION_KEY empty Base64-encoded 32-byte key required for S3 and WebDAV credentials.

Generate an encryption key with:

openssl rand 32 | base64 -w 0

Set the same key permanently before creating remote libraries. Losing it means the encrypted remote credentials cannot be recovered.

OPDS

The catalog is available at:

https://your-host.example/opds/v1/catalog.xml

The feed publishes downloadable EPUB, FB2, TXT, and CBZ chapters. Image directory chapters are not published as acquisitions because they do not have a single source publication.

OPDS clients authenticate with a Tsundoku username and password using HTTP Basic Auth. Use HTTPS and set TSUNDOKU_SECURE_COOKIES=true; the server rejects OPDS Basic Auth when secure cookies are disabled.

Metadata and Notifications

On a title page, an admin can import missing metadata from Open Library by ISBN. The provider link and fetch timestamp are stored, while manually edited title fields remain authoritative.

Readers can subscribe to titles. Successful scans create one in-app notification for each subscriber when a chapter is newly discovered. Existing chapters, rediscovered missing chapters, and failed remote scans do not create duplicate notifications.

Android and Desktop

The frontend is a Tauri 2 application. Desktop builds are handled by the existing desktop release workflow. Android builds are handled by:

.github/workflows/android.yml

The Android workflow initializes the Tauri Android project in CI, builds an arm64 APK, and uploads it as a workflow artifact. Tag builds can additionally produce a signed APK when these repository secrets are configured:

  • ANDROID_KEY_BASE64
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD

On Android, enter the server URL on the setup or login screen. Remote servers must use HTTPS. The native client uses the same server-side sessions as the web client and stores the opaque session transport needed by the mobile HTTP client.

Data and Backup

The default data layout is:

/data/
├── db/tsundoku.db     SQLite database, WAL, and SHM files
├── covers/             Uploaded covers
└── cache/              Disposable page and thumbnail cache

For a consistent backup:

  1. Stop the container, or use SQLite's online backup API.
  2. Save /data/db, /data/covers, and deployment configuration.
  3. Save the source library content separately.
  4. Preserve TSUNDOKU_ENCRYPTION_KEY with the backup when remote libraries are used.

The database contains users, sessions, reading progress, extracted text, full-text indexes, subscriptions, notifications, and external metadata links. The cache can be rebuilt and does not need to be backed up.

Development

Backend

cd backend
CGO_ENABLED=0 go test ./...
CGO_ENABLED=0 go vet ./...
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" ./cmd/tsundoku

Run the suite in a Linux container, not directly on a Windows host. A few path-security tests (symlink traversal out of a library root, in internal/fsx) need a filesystem privilege that Windows requires elevation for; without it, os.Symlink fails before the test can assert anything, and the test reports that failure rather than what it was meant to check. The container also matches CI exactly, since it uses the same pinned image:

docker run --rm -v ${PWD}:/src -w /src/backend \
  -v go-mod-cache:/go/pkg/mod golang:1.26.0 go test ./...

Frontend

cd frontend
npm ci
npm run api:types
npm run lint
npm run check
npm run build

api/openapi.yaml is the source contract. Generated TypeScript types live in frontend/src/lib/api/schema.d.ts; CI fails when regeneration produces a diff.

Production Image

docker build -f deploy/Dockerfile -t tsundoku:local .

The image builds the backend with CGO_ENABLED=0, builds the static frontend, and runs as an unprivileged tsundoku user.

Project Layout

backend/
├── cmd/tsundoku/              Server entrypoint
├── internal/auth/             Users, sessions, passwords, CSRF
├── internal/document/         EPUB, FB2, and TXT parsing
├── internal/httpapi/          REST, reader, OPDS, search, metadata, notifications
├── internal/library/          Storage-neutral scanning and reconciliation
├── internal/media/            Archive and image validation
├── internal/storage/          Local, S3, and WebDAV backends
├── migrations/                Embedded SQLite migrations
└── internal/cache/            Bounded disk cache
frontend/
├── src/routes/                Web reader and admin UI
├── src/lib/api/               Generated OpenAPI types
└── src-tauri/                 Desktop and Android shell
api/openapi.yaml               HTTP contract
deploy/                         Docker image and Compose deployment

Security Notes

  • Use HTTPS in production.
  • Set TSUNDOKU_SECURE_COOKIES=true behind the HTTPS proxy.
  • Set TSUNDOKU_TRUSTED_PROXIES only to proxies you control.
  • Keep TSUNDOKU_ENCRYPTION_KEY outside the database and deployment image.
  • Do not expose the database, covers, or source library mounts directly to the internet.
  • Remote storage endpoints and document inputs are validated and bounded before reading.

License

Tsundoku is licensed under the GNU Affero General Public License v3.0. See LICENSE.

About

A self-hosted manga/manhwa/light novel library and reader, built for speed and cross-platform reach

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages