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.
- 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.
The Docker Compose setup starts Tsundoku on port 8080 and mounts a local
library read-only.
docker compose -f deploy/docker-compose.yml up --buildOpen http://localhost:8080/setup. The one-time setup token is printed in the container logs:
docker compose -f deploy/docker-compose.yml logs -f tsundokuCreate 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 --buildThe compose file uses TSUNDOKU_SECURE_COOKIES=false for local HTTP. Set it to
true when serving through HTTPS.
.cbzarchives 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.
.epubfiles using the EPUB container, OPF manifest, and spine..fb2FictionBook XML files.- UTF-8
.txtfiles.
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.
Create and manage libraries from the owner Libraries page.
Point the library at a directory visible to the server, for example
/libraries/default in Docker.
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.
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.
| 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 0Set the same key permanently before creating remote libraries. Losing it means the encrypted remote credentials cannot be recovered.
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.
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.
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_BASE64ANDROID_KEY_ALIASANDROID_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.
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:
- Stop the container, or use SQLite's online backup API.
- Save
/data/db,/data/covers, and deployment configuration. - Save the source library content separately.
- Preserve
TSUNDOKU_ENCRYPTION_KEYwith 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.
cd backend
CGO_ENABLED=0 go test ./...
CGO_ENABLED=0 go vet ./...
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" ./cmd/tsundokuRun 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 ./...cd frontend
npm ci
npm run api:types
npm run lint
npm run check
npm run buildapi/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.
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.
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
- Use HTTPS in production.
- Set
TSUNDOKU_SECURE_COOKIES=truebehind the HTTPS proxy. - Set
TSUNDOKU_TRUSTED_PROXIESonly to proxies you control. - Keep
TSUNDOKU_ENCRYPTION_KEYoutside 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.
Tsundoku is licensed under the GNU Affero General Public License v3.0. See
LICENSE.