This document provides a comprehensive introduction to Lenny, the open-source Library-in-a-Box system. It covers the system's purpose, core architecture, technology stack, and deployment model. For detailed information about specific subsystems, see: Getting Started for installation and setup, System Architecture for detailed component documentation, Core Components for business logic implementation, External Integrations for third-party service connections, and Deployment for operational procedures.
Lenny is a self-hostable digital lending platform designed for libraries to preserve and lend digital books with full control over their collections. The system implements the OPDS 2.0 catalog syndication standard, integrates with the Readium reading ecosystem for browser-based EPUB consumption, and provides dual authentication modes (OAuth implicit flow and direct OTP-based authentication) to support various client types.
The system operates as a containerized microservices application orchestrated via Docker Compose, with five core services: a FastAPI backend, PostgreSQL database, MinIO S3-compatible storage, Thorium Web reader, and Readium manifest service. Nginx serves as the API gateway, routing requests to appropriate services.
Sources: README.md54-61 README.md81-90
Lenny follows a microservices architecture with clear separation between the API layer, business logic, data persistence, and external integrations. The following diagram maps the high-level system components to their corresponding code modules:
Key Components:
| Component | Code Location | Port | Purpose |
|---|---|---|---|
| Nginx Gateway | docker/nginx/conf.d/lenny.conf | 8080 | Reverse proxy and request routing |
| API Service | lenny/routes/api.py | 1337 | FastAPI HTTP endpoints and route handlers |
| LennyAPI | lenny/core/api.py | N/A | Business logic orchestrator |
| Reader Service | docker/reader/Dockerfile | 3000 | Thorium Web Next.js application |
| Readium Service | ghcr.io/readium:0.6.3 | 15080 | EPUB manifest generation and DRM |
| PostgreSQL | postgres:16 | 5432 | Relational database for Items and Loans |
| MinIO | minio/minio:latest | 9000, 9001 | S3-compatible object storage |
Sources: README.md96-108 Diagram 1 analysis, Diagram 3 analysis
Lenny's technology stack consists of the following components:
| Layer | Technology | Purpose | Version/Details |
|---|---|---|---|
| Container Runtime | Docker | Service containerization | Required by install.sh |
| Orchestration | Docker Compose | Multi-service management | compose.yaml |
| API Gateway | Nginx | Reverse proxy and routing | Includes CORS and sub_filter |
| Backend Framework | FastAPI | RESTful API implementation | v0.115.4 |
| ASGI Server | Uvicorn | FastAPI application server | v0.32.0 |
| ORM | SQLAlchemy | Database abstraction | v2.0.39 |
| Database | PostgreSQL | Persistent data storage | Version 16 |
| Object Storage | MinIO | S3-compatible file storage | Latest |
| HTTP Client | httpx | Async HTTP requests | v0.28.1 with HTTP/2 |
| Reading System | Readium Web SDK | EPUB rendering | v0.6.3 |
| Web Reader | Thorium Web | Browser-based reader UI | Next.js on Node 20 |
| Catalog Standard | OPDS 2.0 | Syndication protocol | pyopds2_lenny package |
| DRM | Readium LCP | Digital rights management | Optional encryption |
| Programming Language | Python | Backend implementation | 3.12 |
Sources: README.md96-108 setup.py54-65
The system exposes HTTP endpoints through Nginx, which routes requests to the appropriate backend services. The following diagram shows the routing configuration and corresponding handler functions:
Primary Endpoints:
| Route Pattern | HTTP Method | Handler Function | Purpose |
|---|---|---|---|
/v1/api/opds | GET | opds_feed() | OPDS 2.0 catalog listing |
/v1/api/opds/{id} | GET | opds_item() | Single item publication |
/v1/api/items/{id}/borrow | GET/POST | borrow_item() | Loan creation with auth |
/v1/api/items/{id}/return | GET | return_item() | Loan finalization |
/v1/api/items/{id}/read | GET | read_item() | Reader access |
/v1/api/upload | POST | upload_file() | Content ingestion |
/v1/api/items/{id}/readium/manifest.json | GET | readium_manifest() | Reading manifest |
Sources: README.md111-118 Diagram 2 analysis, Diagram 3 analysis
Lenny implements a dual-mode authentication system supporting both standard OPDS OAuth implicit flow and a direct OTP-based flow. The mode is controlled by the LENNY_AUTH_MODE environment variable and can be overridden per-request with the ?beta=true query parameter.
Authentication Components:
| Module/Class | Location | Responsibility |
|---|---|---|
verify_session_cookie() | lenny/core/auth.py | Validates session cookies using URLSafeTimedSerializer |
create_session_cookie() | lenny/core/auth.py | Generates signed session cookies with IP binding |
OTP.issue() | lenny/core/auth.py | Requests OTP from external server with rate limiting |
OTP.authenticate() | lenny/core/auth.py | Verifies OTP code against external server |
requires_item_auth | lenny/routes/api.py | Decorator enforcing authentication on endpoints |
otp_issue.html | lenny/templates/otp_issue.html | Email submission form |
otp_redeem.html | lenny/templates/otp_redeem.html | OTP code submission form |
Configuration Variables:
LENNY_AUTH_MODE: Global authentication mode ("oauth" or "direct")LENNY_OTP_SERVER: External OTP service endpointLENNY_SESSION_SECRET: Secret key for signing session cookiesLENNY_SESSION_TTL_SECONDS: Session cookie expiration timeSources: README.md65-78 tests/test_direct_auth_mock.py1-243 lenny/templates/_base_auth.html1-111 Diagram 2 analysis, Diagram 4 analysis
The system's data layer consists of two primary SQLAlchemy models: Item (representing books) and Loan (representing borrowing transactions). The following diagram shows the state transitions and corresponding database operations:
Data Models:
| Model | Table | Key Columns | Purpose |
|---|---|---|---|
Item | items | id, openlibrary_edition, encrypted, num_lendable_total, formats | Represents digital book with metadata |
Loan | loans | id, item_id, patron_email_hash, created_at, returned_at | Tracks borrowing transactions |
Key Methods:
Item.borrow(email): Creates loan record if availableItem.unborrow(email): Finalizes loan and sets returned_atItem.check_availability(): Validates copy count against active loansLoan.create(): Inserts new loan with hashed patron emailLoan.finalize(): Updates returned_at timestampSources: README.md146-190 Diagram 4 analysis
Lenny is deployed as a Docker Compose application with infrastructure automation scripts. The deployment process follows a three-tier architecture: installation → configuration → orchestration.
Installation Entry Points:
| Script | Purpose | Usage |
|---|---|---|
install.sh | Production one-line installer | curl ... | sudo bash |
Makefile | Developer command interface | make all, make build, make restart |
docker/configure.sh | Environment file generation | Generates .env and reader.env |
docker/utils/lenny.sh | Service lifecycle management | --start, --stop, --rebuild |
Docker Services:
The compose.yaml orchestrates five containerized services:
docker/api/Dockerfilepg_isready health checkss3_dataghcr.io/readium:0.6.3All services communicate over the lenny_network bridge network with service discovery via container names.
Operational Commands:
Sources: README.md122-143 README.md161-177 README.md202-265 Diagram 5 analysis
Lenny integrates with three external ecosystems:
OpenLibrary Integration:
/account/otp/issue and /account/otp/redeemopenlibrary.press with X-Lenny-Callback headersReadium Ecosystem:
lenny/core/readium.pyOPDS Clients:
pyopds2_lenny packageSources: README.md92-94 Diagram 6 analysis
Lenny is licensed under AGPL-3.0 and maintained by ArchiveLabs. The project is written in Python 3.12 and classified as Development Status 3 - Alpha. The primary dependencies include FastAPI, SQLAlchemy, httpx, MinIO client, and Uvicorn.
Repository: https://github.com/ArchiveLabs/lenny
Website: https://lennyforlibraries.org/
Contact: [email protected]
Sources: setup.py1-67 README.md340-343
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.