Skip to content

ArchiveLabs/lenny

Repository files navigation

Lenny Logo

Lenny

The open source Library-in-a-Box to preserve and lend digital books.
Learn more »

Issues · Pull Requests · License

Stars Forks Open Issues Pull Requests Last Commit License Ask DeepWiki

📖 Table of Contents


About the Project

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!

🔐 Authentication Modes

Lenny supports multiple authentication modes for patron login and lending:

  1. OTP / Open Library (Default): Standard OPDS authentication flow. Patrons authenticate via a one-time-password email. Clients like Thorium Reader use this via a popup/webview.
  2. Direct Token: A simpler, link-based flow for environments where full OAuth support is tricky. Append ?auth_mode=direct (or legacy ?beta=true) to any OPDS feed URL to activate per-session.
  3. External OAuth / OIDC (optional): Delegate patron authentication to any OIDC-compliant provider (Clerk, Auth0, Okta, Keycloak, Google, etc.) using a PKCE authorization-code flow. Configure the provider in the Admin UI under Settings → Auth. When enabled, the lending mode switches to external and all patron logins are redirected to the provider — no OTP email needed.
  4. IA S3 Patron Auth (optional): Patrons with an Internet Archive account can authenticate directly using their IA S3 access/secret key pair (Authorization: LOW <access>:<secret>). Enabled independently of the lending mode via IA_AUTH_ENABLED (toggle in Admin UI or set in auth.env). Useful for IA-native clients and automation.

To switch back to OTP mode from external auth, set lending mode to ol or none via the Admin UI.


Features

  • 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.
  • Admin UI: Secure admin dashboard served at /admin, isolated from public API access.
  • Encrypted/Unencrypted Item Filtering: Filter catalog items by encryption status via API.
  • External OIDC Patron Auth: Plug in any OIDC provider for patron login — no password management required.
  • IA S3 Patron Auth: Patrons authenticate with Internet Archive S3 credentials; toggled independently of the lending mode.

OPDS 2.0 Feed


Technologies

  • 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

Endpoints

  • /v{1}/api
  • /v{1}/manage
  • /v{1}/read
  • /v{1}/opds
  • /v{1}/stats
  • /admin — Admin UI (internal only, proxied to lenny_admin:4000)

Getting Started

To install and run Lenny as a production application:

curl -fsSL https://raw.githubusercontent.com/ArchiveLabs/lenny/refs/heads/main/install.sh | sudo bash

Development Setup

git clone https://github.com/ArchiveLabs/lenny.git
cd lenny
make all
  • This will generate a .env file 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

Dev vs Production Mode

Lenny defaults to production mode — uvicorn serves requests without watching for file changes. For development with hot-reload:

  1. Set LENNY_PRODUCTION=false in your .env
  2. 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.


Bookserver app sync

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.


Admin Dashboard

Lenny includes a secure admin interface at /admin for managing the library.

Setup

Change these variables in your .env or it will use system generated credentials:

ADMIN_USERNAME=your-username
ADMIN_PASSWORD=your-secure-password

lenny-app Local Development

The admin UI lives in a separate repo (lenny-app). By default Lenny runs the built admin container (lenny_admin) at /admin. To develop lenny-app locally against a running Lenny backend, two changes are needed.

1 — Expose the FastAPI port in compose.yaml

The admin UI communicates with FastAPI directly (bypassing nginx, which blocks /v1/api/admin externally). Add a host-binding to the api service so the local Next.js dev server can reach it:

# compose.yaml — api service ports section
ports:
  - "${LENNY_PORT:-8080}:80"
  - "127.0.0.1:1337:1337"  # local lenny-app dev only — remove for production

Then recreate the container to pick up the new binding:

docker compose up -d --no-deps api

The 127.0.0.1: prefix ensures port 1337 is only accessible from your machine, not the network.

2 — Configure lenny-app .env.local

In the lenny-app repo, create apps/web/.env.local with the following values (copy credentials from your Lenny auth.env):

# Points the Next.js server-side proxy directly to FastAPI (bypasses nginx admin block)
LENNY_INTERNAL_API_URL=http://localhost:1337/v1/api

# Points client-side catalog/public fetches to nginx
NEXT_PUBLIC_API_URL=http://localhost:8080

# Must match ADMIN_INTERNAL_SECRET in lenny/auth.env exactly
ADMIN_INTERNAL_SECRET=<value from auth.env>

# Must match ADMIN_USERNAME / ADMIN_PASSWORD in lenny/auth.env exactly
ADMIN_USERNAME=<value from auth.env>
ADMIN_PASSWORD=<value from auth.env>

Then start the lenny-app dev server:

cd lenny-app
pnpm dev
# admin UI available at http://localhost:3002/admin

Access lenny-app directly at localhost:3002/admin — do not go through nginx at port 8080 during local dev (nginx routes /admin to the built Docker container, not the dev server).

Reverting to the built container

To restore the standard Docker setup, remove the 127.0.0.1:1337:1337 line from compose.yaml and run:

docker compose up -d --no-deps api

Adding Books encrypted or unencrypted

To add a book to Lenny, you must provide an OpenLibrary Edition ID (OLID). Books without an OLID cannot be uploaded.

Adding Books Metadata

Sign in to your Openlibrary.org account.

https://openlibrary.org/books/add

navigate to the above link and add all the details.

Usage using CLI

make addbook olid=OL123456M filepath=/path/to/book.epub [encrypted=true]

Examples

# 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

Important Notes

  • File Location: The EPUB file must be within the project directory (e.g., in ./books/ or project root)
  • OLID Formats: Accepts both OL123456M and 123456 formats
  • Duplicates: If a book with the same OLID already exists, the upload will fail with a conflict.

Troubleshooting

If you get a "File not found" or permission error, make sure:

  1. The file is copied into your lenny project directory.
  2. You're using a relative path from the project root (e.g., ./books/mybook.epub)

Testing Readium Server

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"

Open Library / Internet Archive Auth

Lenny must be connected to an Internet Archive account to enable lending. You can do this two ways: through the Admin UI or the CLI.

Option 1 — Admin UI (recommended)

Open the admin dashboard at /admin, sign in, and navigate to Settings → Open Library. Enter your Internet Archive email and password and click Log in. Lending is enabled immediately — no restart required.

To disconnect, click Log out on the same page. Lending is disabled immediately.

Option 2 — CLI

# Log in (interactive — prompts for email and password)
make ol-login

# Log out — clears IA S3 keys from .env and disables lending
make ol-logout

Scripted / non-interactive login (e.g. CI):

OL_EMAIL=you@example.com LENNY_NONINTERACTIVE=1 make ol-login

LENNY_NONINTERACTIVE=1 suppresses all "are you sure?" confirmation prompts so the command can run unattended in scripts or CI pipelines.

Security: avoid passing OL_PASSWORD as an environment variable in scripts — it will appear in shell history and ps output. Instead, let the interactive prompt handle the password, or pipe it via stdin using a secrets manager.

After logging in, lending is enabled automatically and the API container is restarted so the credentials take effect. After logging out, lending is disabled and the container restarts immediately.


Updating

To update an existing Lenny installation to the latest version:

make update

This 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.

First-time upgrade (existing installations)

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 over

After this, all future updates are just make update — it handles git pull and everything else for you.

Note: Do not run make configure during an upgrade — it would overwrite your .env with 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.


Database Migrations

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.


Health Check

Run diagnostics on your Lenny environment:

make doctor

Checks Docker, .env configuration, database connectivity, disk space, and version status.


Rebuilding

# Rebuild API image and restart (preserves data)
make redeploy

# Full rebuild from scratch (WARNING: wipes database)
make rebuild

Docker Maintenance

Useful commands for keeping the Docker environment healthy on the host machine.

Free Disk Space

Over time, old build layers and untagged images accumulate. Run this periodically (or after a heavy make update) to reclaim disk space:

make prune

Removes dangling images (old untagged builds) and caps the BuildKit cache at 2 GB. Safe — never touches running containers, named volumes, or database data.

Check Resource Usage

docker stats              # live CPU/memory per container
docker system df          # disk usage breakdown (images, containers, volumes, cache)
docker builder du         # build cache size specifically

Useful One-Liners

# See all running Lenny containers
docker ps --filter name=lenny

# Follow logs for a specific service
docker logs -f lenny_api
docker logs -f lenny_admin

# Restart a single service without full redeploy
docker compose restart api

# Shell into the API container
docker exec -it lenny_api bash

Memory Tuning

On machines with limited RAM, tune these in your .env before running make update:

Variable Default Description
LENNY_WORKERS 2 uvicorn worker processes — reduce to 1 on very small machines
LENNY_ADMIN_NODE_HEAP_MB 384 Node.js heap cap for the admin UI — raise on machines with 8+ GB RAM

FAQs

Everything is broken and I need to start from scratch
make tunnel rebuild start preload items=10 log
If I disconnect from the internet and tunnel stops working, what do I do?
make untunnel tunnel start
I am getting database connection problems
make resetdb restart preload items=5
I 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 rebuild

Both commands source reader.env before building, ensuring the reader is configured correctly.


Tests

All automated tests are in the tests/ directory.

To run tests:

pytest
  • Install dependencies:
    pip install -r requirements.txt
  • Test configs via .env.test if needed.

Project Structure

/
├── 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

Contributing

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:

Developers

  • 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.

Community Engagement

  • 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.

Lenny Slack Channel

  • 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


Pilot

We're seeking partnerships with libraries who would like to try lending digital resources to their patrons.


Open Topics

  • Authentication - How does your library perform authentication currently?

Community & Support


📄 License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).


Empowering libraries to share digital knowledge.

About

The open source Library-in-a-Box to preserve and lend digital books.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors