The open source Library-in-a-Box to preserve and lend digital books.
Learn more »
Issues
·
Pull Requests
·
License
- About the Project
- Features
- OPDS 2.0 Feed
- Internet Archive Bookserver app + Lenny OPDS sync
- Technologies
- Endpoints
- Getting Started
- Development Setup
- Updating
- Database Migrations
- Health Check
- Testing Readium Server
- Rebuilding
- FAQs
- Tests
- Project Structure
- Contributing
- Pilot
- Open Topics
- Community & Support
- License
Lenny is a free, open source, Library-in-a-Box for libraries to preserve and lend digital books.
- 📚 Designed for libraries that want control over their digital collections.
- 🔐 Built with modern authentication, DRM, and flexible storage options.
- 🌍 Easy to self-host, customize, and scale for any library size.
- 🚀 Active development and open to contributions!
Lenny supports two authentication modes for lending:
- OAuth Implicit (Default): Standard OPDS authentication flow. Clients like Thorium Reader use this to request a token via a popup/webview.
- Direct Token: A simpler, link-based authentication flow. Useful for environments where full OAuth support is tricky.
- Browser-Friendly: Users authenticate via an OTP (One-Time Password) email directly in the browser.
- How to Enable: This mode is dynamic and applies per-session.
- Trigger: Append
?auth_mode=direct(or legacy?beta=truefor backward compatibility) to any OPDS feed URL (e.g./v1/api/opds?auth_mode=direct). - Sticky Session: Once entered, the session remembers the mode, and all generated links (navigation, shelf, profile) will automatically keep you in that mode.
To switch back to OAuth mode, simply visit the root feed without the parameter (after clearing cookies/session if necessary).
- Full Lending Workflow: Borrow, return, and manage digital books.
- API-first: RESTful endpoints for integration and automation.
- Containerized: Simple Docker deployment and robust Makefile for scripts.
- Book Importer: Quickly load hundreds of test books for demos or pilots.
- Readium Integration: Secure, browser-based reading experience.
- Flexible Storage: S3, Internet Archive, or local file support.
- Database-backed: Uses PostgreSQL and SQLAlchemy.
- Lenny is powered by OPDS 2.0 Specs.Lenny has its own OPDS 2.0 Package
pyopds2_lennymore on pyopds2_lenny repo.
- Docker for deployment and containerization
- nginx as a reverse proxy
- FastAPI (Python) as the web & API framework
- Minio API for storing digital assets
- YAML for configuring library-level rules
- PostgreSQL for the database
- SQLAlchemy as the Python ORM
- Alembic for database migrations
- Readium LCP for DRM
- Readium Web SDK for a secure web reading experience
- OPDS for syndicating holdings
/v{1}/api/v{1}/manage/v{1}/read/v{1}/opds/v{1}/stats
To install and run Lenny as a production application:
curl -fsSL https://raw.githubusercontent.com/ArchiveLabs/lenny/refs/heads/main/install.sh | sudo bashgit clone https://github.com/ArchiveLabs/lenny.git
cd lenny
make all- This will generate a
.envfile with reasonable defaults (if not present). - Navigate to
localhost:8080(or your$LENNY_PORT). - Enter the API container with:
docker exec -it lenny_api bash
Lenny defaults to production mode — uvicorn serves requests without watching for file changes. For development with hot-reload:
- Set
LENNY_PRODUCTION=falsein your.env - Restart:
make redeploy
Now any code change is picked up immediately by uvicorn. To switch back to production mode, set LENNY_PRODUCTION=true and run make redeploy.
Sync your Lenny OPDS feed with Archive.org's Bookserver app. To have a personalized Lenny catalog with a great user interface.
Important
Bookserver app is Internet Archive's closed product, it doesn't come with lenny instance which you can own
make url - Gernerates URl ODPS link & Lenny server + Archive's Book server app sync URL Link.
To add a book to Lenny, you must provide an OpenLibrary Edition ID (OLID). Books without an OLID cannot be uploaded.
Sign in to your Openlibrary.org account.
https://openlibrary.org/books/add
navigate to the above link and add all the details.
make addbook olid=OL123456M filepath=/path/to/book.epub [encrypted=true]# Add an unencrypted book
make addbook olid=OL60638966M filepath=./books/mybook.epub
# Add an encrypted book
make addbook olid=OL60638966M filepath=./books/mybook.epub encrypted=true
# Using numeric OLID format (without OL prefix and M suffix)
make addbook olid=60638966 filepath=./books/mybook.epub- File Location: The EPUB file must be within the project directory (e.g., in
./books/or project root) - OLID Formats: Accepts both
OL123456Mand123456formats - Duplicates: If a book with the same OLID already exists, the upload will fail with a conflict.
If you get a "File not found" or permission error, make sure:
- The file is copied into your lenny project directory.
- You're using a relative path from the project root (e.g.,
./books/mybook.epub)
BOOK=$(echo -n "s3://bookshelf/32941311.epub" | base64 | tr '/+' '_-' | tr -d '=')
echo "http://localhost:15080/$BOOK/manifest.json"
curl "http://localhost:15080/$BOOK/manifest.json"To update an existing Lenny installation to the latest version:
make updateThis single command handles everything automatically:
- Pulls the latest code (
git pull --ff-only) - Syncs new environment variables (never overwrites your existing config)
- Pulls updated Docker images
- Backs up your database before rebuilding
- Rebuilds and restarts containers
- Applies database migrations automatically on startup
Your data (database, books, S3 storage) is preserved across updates. A database backup is saved to backups/ before every update. If anything goes wrong, re-run make update — every step is idempotent.
If your Lenny installation predates the update engine (no make update command yet), you need a one-time manual bootstrap:
git pull # get the update engine code (one-time only)
make update # from here, the engine takes overAfter this, all future updates are just make update — it handles git pull and everything else for you.
Note: Do not run
make configureduring an upgrade — it would overwrite your.envwith new credentials. The update engine syncs new variables safely without touching your existing configuration.
For details on the update engine architecture, see docs/plans/update-engine.md.
Lenny uses Alembic for database migrations. Migrations run automatically on container startup — no manual steps needed during normal use.
make migrate # Run pending migrations
make migrate-status # Show current migration state
make migration msg="add new table" # Generate a new migration (developers only)
make migrate-rollback # Rollback last migration (use with caution)For full details, see docs/MIGRATIONS.md.
Run diagnostics on your Lenny environment:
make doctorChecks Docker, .env configuration, database connectivity, disk space, and version status.
# Rebuild API image and restart (preserves data)
make redeploy
# Full rebuild from scratch (WARNING: wipes database)
make rebuildEverything is broken and I need to start from scratch
make tunnel rebuild start preload items=10 logIf I disconnect from the internet and tunnel stops working, what do I do?
make untunnel tunnel startI am getting database connection problems
make resetdb restart preload items=5I need to stop services (also kills the tunnel)
make stop The /v1/api/items/{id}/read endpoint redirects to Nginx default page
This happens when using docker compose up -d directly instead of make start or make build.
Why it happens: The Thorium Web reader requires NEXT_PUBLIC_* environment variables at build time. When running docker compose up -d directly, these variables may not be passed correctly.
Solution: Use the Makefile commands which properly source the environment:
# Fast build (uses cache)
make build
# Full rebuild (no cache)
make rebuildBoth commands source reader.env before building, ensuring the reader is configured correctly.
All automated tests are in the tests/ directory.
To run tests:
pytest- Install dependencies:
pip install -r requirements.txt - Test configs via
.env.testif needed.
/
├── lenny/ # Core application code
│ ├── configs/ # App configuration (reads from .env)
│ ├── core/ # Database models, ORM, business logic
│ └── routes/ # API route definitions and docs
├── alembic/ # Database migration scripts
│ └── versions/ # Individual migration files
├── docker/ # Docker configuration
│ └── utils/ # Utility scripts (lenny.sh, update.sh, doctor.sh)
├── scripts/ # Utility scripts (e.g. preload.py)
├── tests/ # Automated tests
├── Makefile # Make commands for setup/maintenance
├── install.sh # Production install script
├── VERSION # Current release version
├── .env # Environment variables (generated)
└── README.md # Project documentation
There are many ways volunteers can contribute to the Lenny project, from development and design to data management and community engagement. Here’s how you can get involved:
- Getting Started: Check out our Development Setup for instructions on how to set up your development environment, find issues to work on, and submit your contributions.
- Good First Issues: Browse our Good First Issues to find beginner-friendly tasks.
- Join our Community Calls: Open Library hosts weekly community Zoom call for Open Library & Lenny and design calls. Check the community call schedule for times and details.
- Ask Questions: If you have any questions, request an invitation to our Slack channel on our volunteers page.
- If you are a Developer or an library instrested in contributing or trying lenny feel free to join our lenny slack channel from Here
For more detailed information on community call. refer to Open Libraries page Here
We're seeking partnerships with libraries who would like to try lending digital resources to their patrons.
- Authentication - How does your library perform authentication currently?
- GitHub Issues — File bugs, request features, ask questions
- Email: mek@archive.org
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
Empowering libraries to share digital knowledge.