E-book management with a web-based reader (Rails + React) and a companion Android reader app.
日本語版 README はこちら / Japanese README
A guided tour (signup → library scan → grid/list browse → CBZ reader → horizontal EPUB → vertical EPUB) recorded with Playwright. See docs/demo.mp4 (≈1.3 min, 2.9 MB, H.264).
demo.mp4
Regenerate it with:
cd client && npm run demo:video
# Playwright outputs client/test-results/.../video.webm. Transcode it to mp4
# so GitHub's README viewer can play it inline:
ffmpeg -y -i client/test-results/tour-Bookwall-guided-tour-desktop-chromium/video.webm \
-c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p -movflags +faststart -an \
docs/demo.mp4| Directory | Role | Stack |
|---|---|---|
server/ |
API + OPDS delivery, SQLite + Active Storage, book scanner, authentication | Rails 8.1 / Ruby 4.0 / Falcon / Thruster / SQLite (FTS5) / SolidQueue |
client/ |
SPA mounted under /ui. Book list / detail / search / favorites / collections / settings / Web Reader |
Vite + React 19 + TypeScript / Tailwind v4 / shadcn/ui / React Router / TanStack Query / Zustand / foliate-js / PDF.js |
apps/android/ |
OPDS reader app: catalog browsing, streaming + offline reading, two-way progress sync | Kotlin / Jetpack Compose / Hilt / Room / WorkManager / Coil / foliate-js (WebView) |
- CBZ (ComicInfo.xml supported)
- EPUB
- Image directories
- EPUB extended metadata:
dc:subjectis imported as tags, and Calibre'scalibre:series/calibre:series_indexas series name and volume number. - Series fallback: Books without series metadata fall back to the parent directory name.
- Cover image: The first page is extracted per format and stored via Active Storage.
- Full-text search: Title / series / author AND search powered by SQLite FTS5.
- Taxonomy browsing: Index pages for series, authors, and tags with thumbnails. Library detail can toggle between book view and series view.
- Sorting: Newest, registration date, title, series, and author (ascending / descending).
- Collections: User-defined collections of books, also served as OPDS feeds.
- Bulk actions: Multi-select books to favorite / unfavorite / delete at once.
- Library sharing: Share a library with selected users (read-only for non-owners).
- Settings: Register library paths through a server-side directory browser, and toggle the daily scheduled scan.
- CBZ / PDF / image directories: Pages are streamed from the server and rendered client-side. Supports two-page spread / single page, LTR / RTL, and four scale modes.
- EPUB: Built on foliate-js. Table of contents, font size, theme (light / dark / sepia), and writing direction (auto / horizontal / vertical) are stored per book.
- Reading progress: Page position (CBZ) and CFI + fraction (EPUB) are auto-saved and reflected on covers, the home carousels, and the detail page.
- Scrubber: Jump directly to any page, with thumbnail (CBZ) / chapter (EPUB) previews.
- Keyboard navigation: Page turning, spread toggle, and single-page nudge;
?shows the shortcut list. - Series roll-over: Advancing past the last page continues with the next volume in the series.
- OPDS / OPDS-PSE: Atom feeds for other reader apps — recent / recent reads / favorites / per-library / series / tags / collections, with tag facets and page streaming (PSE).
- Progress sync: A first-party progress endpoint is advertised on the OPDS root as a capability link; the Android app pushes and pulls read positions through it.
- Authentication: Cookie sessions (for UI) and Bearer tokens (for OPDS / Reader) coexist.
- Mobile support: Works on viewports starting from 390px.
- OPDS client: Register multiple servers (Basic auth / self-signed certificates), browse catalogs with sort / filter / tag facets.
- Readers: Image books stream via OPDS-PSE (spread view, RTL); EPUB renders in the same foliate-js engine as the web reader, so CFI progress is interchangeable.
- Offline cache: Background downloads with a size limit, a downloads screen that works fully offline, and auto-caching of books you read. Progress recorded offline syncs back when connectivity returns.
- Ruby 4.0.3+
- Node.js 22+ / npm
- SQLite 3 (build with FTS5 enabled)
(cd server && bundle install && bin/rails db:setup)
(cd client && npm install)cd server
bin/dev # Starts web (Falcon, :3000) and client (Vite, :5173) together via foremanSee Dockerfile.
docker build -t bookwall .
docker run -d --name bookwall \
-p 8237:8237 \
-e RAILS_MASTER_KEY="$(cat server/config/master.key)" \
-v /path/to/your/bookwall-config:/config \
-v /path/to/your/books:/books:ro \
--restart unless-stopped \
--init \
bookwall- Thruster listens on port 8237 and proxies to the internal Falcon (
TARGET_PORT=3000). - The
/configvolume holds the SQLite databases (primary / cache / queue / cable) and the Active Storage cover files. - Mount your actual library (CBZ / EPUB / PDF, etc.) as a separate volume and register its path as a library from Bookwall's settings screen.
┌──────────────────────────┐
│ Browser / OPDS Reader │
└────────────┬─────────────┘
│ HTTP/1.1, HTTP/2 (Thruster)
▼
┌──────────────────────────┐
│ Thruster (port 8237) │ ← Docker only
│ static asset cache │
│ X-Sendfile acceleration │
└────────────┬─────────────┘
│ proxy → :3000
▼
┌──────────────────────────┐
│ Falcon (Async/Fiber) │
│ Rails 8.1 API + SPA fwd │
└────────────┬─────────────┘
┌─────────────────┼─────────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌──────────────────┐
│ /api/* │ │ /opds/* │ │ /ui/* (SPA) │
│ JSON, Alba │ │ Atom + PSE │ │ React + Tailwind │
│ Cookie sess. │ │ Bearer / Basic │ │ TanStack Query │
└───────┬────────┘ └───────┬────────┘ └──────────────────┘
│ │
▼ ▼
┌────────────────────────────────────┐
│ ActiveRecord (SQLite, WAL, FTS5) │
│ Active Storage (local disk) │
│ SolidQueue (background jobs) │
└────────────────────────────────────┘
│ enqueue
▼
┌──────────────────────────┐
│ SolidQueue jobs │
│ ScanLibraryJob │
│ DestroyLibraryJob │
│ Books::FtsSyncJob │
│ Scheduled* (cron) │
│ Concurrent::ThreadPool │
│ Parsers (CBZ/EPUB/...) │
│ Covers::Extractor │
└──────────────────────────┘
In addition, / redirects to /ui/, and Rails' built-in /up health check endpoint is routed. The Scheduled* jobs (ScheduledLibraryScanJob / ScheduledCleanupJob) are kicked off daily by SolidQueue per config/recurring.yml.
Book files are stored in Book.file_path as paths relative to the library root, and resolved at runtime as library.path + file_path.
bookwall/
├── README.md # ← this file
├── README-ja.md # Japanese translation
├── LICENSE # MIT
├── Dockerfile # multi-stage (client build → server image)
├── .dockerignore
├── .gitignore
├── CLAUDE.md # Project requirements (for Claude Code)
├── docs/ # demo.mp4 (Playwright guided-tour recording, H.264)
├── server/ # Rails 8.1 API + OPDS. See server/README.md
├── client/ # Vite + React + TS SPA. See client/README.md
└── apps/
└── android/ # Android OPDS reader app. See apps/android/README.md
The provenance and licenses of the test book fixtures under server/spec/fixtures/files/ (CBZ / EPUB / PDF / image directories) are documented in server/spec/fixtures/files/SOURCES.md.
MIT License.