Protect EPUB and PDF files with Readium LCP DRM.
- Encrypt books
- Generate licenses
- Self-hosted
- REST API
- Docker ready
- PostgreSQL support
- Works with Thorium Reader
docker pull ghcr.io/amirhdev/ebook-lcp-server:latestdocker compose up --buildsh scripts/demo-local.shCoverage report:
- Repo summary:
reports/coverage/README.md - Regenerate locally:
make coverage
Zero-setup quickstart:
curl -sSL https://raw.githubusercontent.com/amirHdev/ebook-lcp-server/main/quickstart.sh | shThe project is centered on the normal LCP workflow: ingest a book, protect it, create licenses for readers, and expose enough API around that flow to run it as a service.
- REST and GraphQL APIs for publications, licenses, status, and admin tasks
- JWT auth with admin and publisher roles
- PostgreSQL-backed repositories
- Readium
lcpencryptintegration for EPUB and PDF processing - Admin UI
- OpenAPI files and docs endpoints
- Audit log endpoint
- Webhook delivery
- Per-tenant publication and license scoping
- Docker, Kubernetes, K3s, ArgoCD, Prometheus, and Grafana files
- Public-domain EPUB example under
examples/pride-and-prejudice - Native-style integrations for Calibre, calibre-web, and Kavita-style workflows
It is still moving, but the main pieces are here already.
For local work, the easiest path is the Compose stack. It gives you the API, the Readium sidecars, storage, and the small admin surface in one place.
docker compose up --buildServices started by the current compose file:
| Service | URL |
|---|---|
| API | http://localhost:8080 |
| Admin UI | http://localhost:5173 |
| PostgreSQL | localhost:5432 |
| Readium LCP server | http://localhost:8989 |
| Readium LSD server | http://localhost:8990 |
| Swagger UI | http://localhost:8081 |
| MinIO API | http://localhost:9000 |
| MinIO console | http://localhost:9001 |
| Prometheus | http://localhost:9090 |
| Grafana | http://localhost:3000 |
The admin UI uses these local credentials:
username: admin
password: admin
2FA code: 123456
The compose file is meant to bring up the whole local stack, including the Readium LCP and LSD services. It also includes MinIO for the S3 storage path.
Run the local demo after the containers are up:
sh scripts/demo-local.shIt uploads the sample EPUB, creates a license, and prints the publication ID, license ID, and license URL.
For import automation, see docs/integrations.md, integrations/lcp_forwarder.py, integrations/lcp_library_watcher.py, and the Calibre plugin source under integrations/calibre_plugin.
The repo now includes small reference SDKs for the current API surface:
- TypeScript:
sdk/typescript - Python:
sdk/python
TypeScript smoke test:
./frontend/node_modules/.bin/tsc -p sdk/typescript/tsconfig.json
node sdk/typescript/dist/examples/smoke.jsPython smoke test:
PYTHONPATH=sdk/python python3 sdk/python/examples/smoke.pyThe API is token-based. The built-in login route is mainly useful for local work and demos; in a real deployment you will likely put stronger identity management around it.
Get a token:
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin","twoFactor":"123456"}'Check service status:
curl http://localhost:8080/api/v1/lcp/status \
-H "Authorization: Bearer $TOKEN"Upload the sample EPUB and create a license:
sh scripts/demo-local.shDocs endpoints:
http://localhost:8080/swagger.yamlhttp://localhost:8080/swagger.jsonhttp://localhost:8080/docs/openapi.yamlhttp://localhost:8080/docs/swagger.json
Public API docs: https://amirhdev.github.io/ebook-lcp-server/
Postman collection: docs/postman/lcp-server.postman_collection.json
There is a sample book in the repo so you do not need to hunt for test content before trying the flow.
The repo includes a public-domain copy of Pride and Prejudice:
examples/pride-and-prejudice/pride-and-prejudice.epubSee examples/pride-and-prejudice/README.md for the demo notes.
The flow is based on Readium LCP and is intended for LCP-compatible readers such as Thorium Reader. See docs/reader-compatibility.md for the current compatibility matrix and the reader demo workflows. Fixtures used while checking compatibility live under examples/lcp-fixtures, and repeatable Readium Swift / Android demo bundles can be generated with scripts/export-reader-demo.sh.
docs/certification-blueprint.md explains the evidence bundle to gather before an official EDRLab certification run. scripts/certification-smoke.sh emits a machine-readable local report from a running stack, and scripts/generate-certification-packet.sh builds a fuller local packet with a sample license flow, LCPL artifact, status document, admin snapshots, and deployment config captures. docs/certificate-swap-guide.md covers the move from the bundled test certificate to a real production certificate.
The official Readium server is the reference implementation. This project takes a more batteries-included path around the same LCP ecosystem.
| Area | ebook-lcp-server |
readium/readium-lcp-server |
|---|---|---|
| Main focus | Self-hosted API service with a ready local stack | Reference LCP server components |
| Local start | One Compose stack for API, PostgreSQL, MinIO, Swagger UI, and Readium sidecars | Install and run separate Go binaries |
| API surface | REST and GraphQL around publications, licenses, admin tasks, metrics, and docs | License server and status server APIs |
| Storage path | Filesystem or S3-compatible storage, including signed URLs | Filesystem or S3 through lcpencrypt |
| Database path | PostgreSQL-first in the local stack | SQLite, MySQL, SQL Server, or PostgreSQL |
| Multi-tenant behavior | Tenant scoping, tenant API keys, tenant webhooks, storage prefixes, and rate limits | Not presented as a built-in tenant layer in the upstream README |
| Developer extras | README demo, Compose stack, Postman collection, hosted docs, audit logs, webhooks, metrics | Upstream CLI/server components and test frontend |
Use the official project when you want the upstream reference pieces directly. Use this repo when you want a more packaged self-hosted service around them.
The defaults are aimed at local development. Start from the example file, then change the values that describe your own deployment.
Start from .env.example:
cp .env.example .envThe main settings are:
| Variable | Purpose |
|---|---|
DB_DSN |
PostgreSQL connection string |
LCP_PROVIDER_URI |
Public provider URI written into licenses |
LCP_CORE_URL |
Readium LCP core service URL |
LCP_CORE_USER, LCP_CORE_PASSWORD |
Credentials for the Readium core service |
LCP_STORAGE_FS_DIR |
Directory used for encrypted publication files |
LCP_S3_ENDPOINT |
S3-compatible endpoint, such as localhost:9000 for MinIO |
LCP_S3_PUBLIC_ENDPOINT |
Public endpoint used when generating signed download URLs |
LCP_S3_REGION |
S3 region |
LCP_S3_BUCKET |
Bucket for encrypted publication files |
LCP_S3_ACCESS_KEY, LCP_S3_SECRET_KEY |
S3 credentials |
LCP_S3_USE_SSL |
Whether the S3 endpoint uses TLS |
LCP_S3_SIGNED_URL_TTL_SECONDS |
Lifetime for signed download URLs |
JWT_SECRET |
Secret used to sign API tokens |
ADMIN_USERNAME, ADMIN_PASSWORD |
Admin login |
PUBLISHER_USERNAME, PUBLISHER_PASSWORD |
Publisher login |
DEFAULT_TENANT_ID |
Tenant ID included in locally issued login tokens |
PUBLIC_BASE_URL |
Public base URL used in generated links |
STATUS_BASE_URL |
License status server base URL |
RATE_LIMIT_RPM |
Per-subject request limit for protected routes |
WEBHOOK_URLS |
Comma-separated webhook targets |
WEBHOOK_SECRET |
Optional secret used to sign webhook payloads |
For first-time local work, start by checking these groups:
- Auth:
JWT_SECRET,ADMIN_USERNAME,ADMIN_PASSWORD,ADMIN_2FA_CODE - Storage:
LCP_STORAGE_MODE,LCP_STORAGE_FS_DIR, and theLCP_S3_*values if you use MinIO or S3 - Service URLs:
PUBLIC_BASE_URL,STATUS_BASE_URL, andLCP_CORE_URL
The placeholder values in .env.example are safe to copy for a local demo, but replace secrets,
provider URLs, certificates, and storage credentials before using a shared or production environment.
Set LCP_STORAGE_MODE=s3 to store encrypted publication files in S3-compatible storage such as MinIO. When S3 mode is enabled, /publications/{id}/content redirects to a short-lived signed URL instead of streaming the object through the API server. The default mode is still fs.
The service also has a few integration features that are useful once more than one system is involved:
Webhook events:
publication.uploadedlicense.createdlicense.revoked
Publications and licenses carry a tenantId. Tokens issued by the built-in login flow include DEFAULT_TENANT_ID, and reads are scoped to that tenant.
Admin audit entries are available at GET /api/v1/admin/audit.
If you prefer to run parts of the stack yourself while working on the code, the API and frontend can still be started separately.
Run the API directly:
cp .env.example .env
export $(grep -v '^#' .env | xargs)
go run ./cmd/serverRun the frontend:
cd frontend
npm ci
npm run devThe Vite dev server starts the admin UI on http://localhost:5173. Keep the Go API running on
http://localhost:8080; the frontend sends API requests to that local backend during development.
If the UI loads but data is missing, confirm both servers are running and that browser requests to
localhost:8080 are not blocked by CORS or a stale proxy setting.
Run tests:
go test ./...The repo now includes a small operator CLI under cmd/lcpctl for the most common self-hosting tasks.
Health and readiness:
go run ./cmd/lcpctl health --base-url http://127.0.0.1:8080Get a JWT:
go run ./cmd/lcpctl login \
--base-url http://127.0.0.1:8080 \
--username admin \
--password admin \
--two-factor 123456Run the full upload-and-license smoke flow:
go run ./cmd/lcpctl demo \
--base-url http://127.0.0.1:8080 \
--username publisher \
--password publisherUpload a publication directly:
go run ./cmd/lcpctl upload \
--base-url http://127.0.0.1:8080 \
--username publisher \
--password publisher \
--file examples/pride-and-prejudice/pride-and-prejudice.epubCreate or revoke a license:
go run ./cmd/lcpctl license create \
--base-url http://127.0.0.1:8080 \
--username publisher \
--password publisher \
--publication-id <publication-id>go run ./cmd/lcpctl license revoke \
--base-url http://127.0.0.1:8080 \
--username publisher \
--password publisher \
--id <license-id>The repo contains both a plain Docker path and Kubernetes manifests. The Kubernetes side is more complete today, especially around the Readium core services.
Docker:
docker build -t lcp-server:local .
docker run -p 8080:8080 --env-file .env lcp-server:localPublished image:
docker pull ghcr.io/amirhdev/ebook-lcp-server:latestKubernetes:
kubectl apply -k deploy/overlays/prodHosted deployment guides:
docs/deploy.mddocs/deploy-flyio.mddocs/deploy-railway.md
K3s scripts, ArgoCD files, monitoring manifests, and production notes are also in the repo.
The short version is below. The full file is more useful if you want to see the order of work and release gates.
- Hosted OpenAPI docs
- One-click deployment examples
- Reader demos for Thorium, Readium Swift, and Android
The fuller implementation plan is in docs/roadmap.md.
cmd/server: HTTP server wiringinternal/adapter/rest: REST handlersinternal/adapter/graphql: GraphQL schema and resolversinternal/usecase/lcp: publication and license logicinternal/adapter/repository/lcp: in-memory and PostgreSQL repositoriesfrontend: admin UIdocs: API, architecture, deployment, and operations docsdeploy: Kubernetes, K3s, monitoring, registry, and ArgoCD manifestsexamples: sample books and LCP fixtures
docs/architecture.mddocs/deployment-guide.mddocs/PRODUCTION.mddocs/security-policy.mddocs/user-manual.mddocs/contract-matrix.mddocs/openapi-rest.yamldocs/roadmap.md



