Your O'Reilly Learning library, yours to keep.
Export any book to EPUB 3 or PDF from a single, beautiful local web app.
Features · Quick Start · Architecture · Configuration · Contributing · Changelog
Caution
Disclaimer — read before use. Ryliox is for personal and educational use only. By using it, you agree to the O'Reilly Terms of Service. The authors are not affiliated with O'Reilly Media and assume no liability for how the tool is used. Only export content you have legal access to.
| Feature | What you get |
|---|---|
| EPUB 3 export | Clean, reflowable e-books built with EbookLib |
| PDF export | Print-quality rendering via WeasyPrint |
| Persistent download queue | Jobs survive restarts — persisted in SQLite |
| Live progress | Real-time status streamed with Server-Sent Events |
| Session vault | Cookie-based auth, SQLite-first storage with Fernet encryption |
| Microkernel architecture | Auth, book, chapters, assets, EPUB and PDF as hot-pluggable plugins |
| OWASP-aware defaults | Same-origin checks, security headers, CSP, rate limiting, audit logging with HMAC |
| Observability | Prometheus metrics, health probes, typed Pydantic configuration |
| Internationalized UI | i18next-powered Astro 7 + React 19 + Tailwind CSS 4 frontend |
| One-command Docker | docker compose up -d and you're done |
| Full test matrix | Unit, integration, contract, security, e2e, a11y and performance suites |
git clone https://github.com/ZnOw01/Ryliox.git
cd Ryliox
docker compose up -dThen open http://localhost:8000.
Warning
The Compose port is bound to 127.0.0.1 intentionally. Ryliox does not authenticate clients of its own API, and the cookie endpoint can return stored O'Reilly cookie values to the local UI. Never expose port 8000 to a LAN or the public internet without an authenticating reverse proxy — see Secure Deployment.
Requirements
| Tool | Version | Notes |
|---|---|---|
| Python | 3.11 · 3.12 · 3.13 | Managed with uv |
| Bun | 1.3+ | Frontend toolchain |
| Node.js | 22.13+ or 24+ | Required by Bun/Astro tooling |
| GTK3 runtime | Windows only | Needed by WeasyPrint for PDF export — GTK for Windows |
git clone https://github.com/ZnOw01/Ryliox.git
cd Ryliox
uv sync --extra dev
python -m launcher --no-browserThen open http://localhost:8000.
The launcher creates an isolated Python environment in .run/venv, installs frontend dependencies with Bun, builds the frontend when needed, and starts the FastAPI app. Use python -m launcher --docker to run through Docker Compose instead.
Ryliox needs your O'Reilly session cookies to access purchased or subscribed content.
- Open the web UI.
- Click Set cookies.
- Paste a full JSON cookie export from a browser extension such as EditThisCookie.
Important
Use a full browser export, not a console snippet. Only the export can include the HttpOnly orm-rt refresh cookie — document.cookie cannot read HttpOnly cookies, so sessions created that way expire quickly.
Cookie storage is SQLite-first:
- The UI writes cookies to
data/session.sqlite3throughPOST /api/cookies. GET /api/cookiesreads from the same SQLite store.data/cookies.jsonis only a legacy import path — once SQLite has cookies, editing it has no effect.
Warning
Protect the local account, filesystem, backups and Docker volume that contain data/session.sqlite3. Deleting cookies from the UI or the database does not revoke an O'Reilly session already copied elsewhere.
flowchart TB
subgraph Client["Browser"]
UI["Astro 7 + React 19<br/><code>frontend/</code>"]
end
subgraph Server["FastAPI application — <code>web/</code>"]
direction TB
MW["Security middleware"]
API["REST routes"]
SSE["SSE progress stream"]
end
subgraph Core["Core — <code>core/</code>"]
direction TB
SVC["Services"]
REPO["Repository · DTOs · Mappers"]
KRN["Microkernel"]
end
subgraph Plugins["Plugins — <code>plugins/</code>"]
direction LR
P1["auth"]
P2["book"]
P3["chapters"]
P4["assets"]
P5["epub"]
P6["pdf"]
end
DB[("SQLite<br/><code>data/*.sqlite3</code>")]
OR["O'Reilly V2 API"]
UI --> MW
MW --> API & SSE
API --> SVC
SVC --> REPO --> DB
SVC --> KRN --> Plugins
P2 & P3 & P4 --> OR
classDef external fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-dasharray:4 3
class DB,OR external
A download, step by step:
sequenceDiagram
autonumber
actor U as User
participant F as Frontend
participant A as FastAPI
participant Q as Queue (SQLite)
participant W as Worker
participant O as O'Reilly API
U->>F: Save cookies · pick a book
F->>A: POST /api/download
A->>Q: Enqueue job (persisted)
A-->>F: job_id
Q->>W: Dequeue
loop Each chapter
W->>O: Fetch chapter + assets
O-->>W: HTML · images · CSS
W->>Q: Persist progress
Q-->>F: SSE update (live %)
end
W->>W: Build EPUB 3 / PDF
W-->>U: Saved to output/
Runtime flow, in words:
- The user saves cookies through the UI or API.
- The frontend searches books and requests metadata through
/api/*. POST /api/downloadvalidates the request and enqueues a job.DownloadQueueServicepersists the job in SQLite.- A worker runs
DownloaderPlugin, which fetches chapters, assets and output formats. - Progress is persisted and streamed through
/api/progress/stream.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/status |
Auth and cookie status |
GET |
/api/search?q= |
Search books |
GET |
/api/book/{id} |
Book metadata |
GET |
/api/book/{id}/chapters |
Chapter list |
POST |
/api/download |
Queue a download |
GET |
/api/progress?job_id= |
Job progress snapshot |
GET |
/api/progress/stream |
Live progress stream |
POST |
/api/cancel |
Cancel a job |
POST |
/api/cookies |
Save session cookies |
GET |
/api/health |
Liveness probe |
GET |
/api/health/detailed |
Disk, memory, SQLite, external APIs |
GET |
/metrics |
Prometheus metrics |
GET |
/api/docs |
Swagger UI (development) |
Configuration is loaded from .env into a typed Pydantic settings model. Start from the fully commented .env.example.
| Area | Examples | Notes |
|---|---|---|
| Server | HOST, PORT, APP_VERSION |
Bind address and app metadata |
| HTTP | REQUEST_*, USER_AGENT, ACCEPT_* |
Outbound client behavior |
| Security | ENVIRONMENT, CSP_POLICY, ALLOWED_HOSTS |
Headers, host checks, CSP |
| Rate limit | RATE_LIMIT_*, API_RATE_LIMIT_* |
Endpoint throttling |
| Secrets | SECRET_MASTER_PASSWORD |
Encryption and rotation |
| Audit | AUDIT_* |
Audit log retention and integrity |
| Cache | CACHE_* |
Resource cache sizes and TTLs |
Runtime paths default to local, git-ignored directories:
| Path | Contents |
|---|---|
data/ |
SQLite databases, cookies, audit logs, secrets |
output/ |
Generated EPUB / PDF files |
.run/ |
Launcher-managed local runtime files |
Localhost keeps the zero-configuration workflow. A non-loopback bind — and every production deployment — requires:
| Variable | Requirement |
|---|---|
RYLIOX_SECURITY__ADMIN_TOKEN |
At least 32 characters. Remote clients send it as Authorization: Bearer <token>; it is never compiled into the frontend bundle |
RYLIOX_SESSION__ENCRYPTION_KEY |
Fernet key (or RYLIOX_SESSION__ENCRYPTION_KEY_FILE) |
RYLIOX_AUDIT__HMAC_KEY |
Audit-log integrity key (or its _FILE variant) |
RYLIOX_SESSION__OLD_ENCRYPTION_KEYS |
Optional JSON list of old cookie keys while rotating |
Migrations & filesystem safety
- On first startup, plaintext cookie rows and legacy cookie files are imported, encrypted transactionally, and the plaintext source is removed. Back up before upgrading.
- Development keys are stored with mode
0600in the platform user configuration directory, separate from SQLite and audit ciphertext. - Output paths — including values selected by the local native picker — must remain below
RYLIOX_PATHS__OUTPUT_ROOT(default./output). The native picker is disabled for remote binds.
uv sync --extra dev # install Python dependenciesBackend checks
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pytestFrontend checks
cd frontend
bun run typecheck
bun run test
bun run format:check
bun run buildSecurity checks
uv sync --extra security
uv run pytest tests/security -q --run-securityTip
If uv run tries to reuse a broken .venv on Windows, point uv at a clean ignored environment:
$env:UV_PROJECT_ENVIRONMENT=".run\dev-venv"
uv sync --extra devbun test --run fails with Vitest errors
Use bun run test. The test script runs vitest run with the configured JSDOM environment.
uv run fails on .venv/lib64 with access denied
The local .venv is likely incomplete or has a Windows symlink permission problem. Use an ignored project environment:
$env:UV_PROJECT_ENVIRONMENT=".run\dev-venv"
uv sync --extra devPDF export fails on Windows
Install the GTK3 runtime linked in Quick Start. WeasyPrint needs native libraries to render PDFs.
Cookies save but auth expires quickly
Use a full browser cookie JSON export. Console-based cookie snippets cannot read the HttpOnly orm-rt refresh cookie — see Cookie Setup.
Ryliox/
├── config.py Typed settings and legacy module-level constants
├── main.py Thin server entry point
├── launcher/ Local workflow launcher (venv, frontend, Docker)
├── web/ FastAPI app, middleware, schemas, routes
├── core/ Services, repository, DTOs, kernel, security, storage
├── plugins/ Auth, book, chapter, asset, EPUB, PDF, downloader
├── utils/ File and filename helpers
├── frontend/ Astro 7 + React 19 + Tailwind CSS 4
├── tests/ Unit · integration · contract · security · e2e · a11y · performance
├── Dockerfile Multi-stage production image
├── docker-compose.yml Local orchestration (loopback-only)
└── epubcheck/ EPUB validation assets
- Unified FastAPI backend with persistent SQLite queue
- Astro 7 + React 19 frontend with i18n
- Encrypted session storage and audit logging
- Standalone CLI mode
- Prebuilt Docker image published to GHCR
- EPUB validation in CI (epubcheck)
- More UI languages
Contributions of all kinds are welcome — bug reports, features, docs and translations. Read the Contributing Guide and follow the Code of Conduct.
# The short version
git checkout -b feat/my-change
uv run ruff check . && uv run mypy && uv run pytest
# open a PRRyliox is a from-scratch refactor inspired by:
Distributed under the MIT License — see LICENSE.
If Ryliox helps you, consider giving it a ⭐ — it helps others find it!