Skip to main content
Version: v5.3.0

Local Development

Run OpenReader from a checkout to work on it. To run it from source without changing code, Docker Compose with a local-build file is usually simpler.

Prerequisites​

ToolNeededWhy
Node.js LTS and pnpmAlwaysRuns the app. The repository pins the pnpm version.
weed (SeaweedFS)Unless you use external S3Embedded object storage.
nats-serverUnless you use an external workerThe embedded worker's job queue.
soffice (LibreOffice)OptionalDOCX conversion. Without it, DOCX uploads fail.

FFmpeg and the worker's models need no install: pnpm i brings an FFmpeg build (FFMPEG_BIN overrides it), and models download into docstore/model the first time they are needed.

brew install node pnpm seaweedfs nats-server
brew install --cask libreoffice # optional

Run it​

git clone https://github.com/richardr1126/openreader.git
cd openreader
pnpm i
cp .env.example .env

In .env, set:

BASE_URL=http://localhost:3003
AUTH_SECRET=<output of: openssl rand -base64 32>

That is a complete configuration. The app starts its own storage, queue and compute worker, and generates or derives the worker and playback secrets. Keep AUTH_SECRET stable: it also encrypts saved provider keys.

pnpm dev

Open http://localhost:3003. If anything is missing or misconfigured, startup lists every problem at once and exits.

To run a production build instead, use pnpm build then pnpm start. The build compiles the worker to JavaScript; pnpm dev runs it from its TypeScript sources.

Migrations and scheduled tasks run automatically, as on any self-hosted install.

Optional settings​

Add any of these to the same .env.

First administrator. Create an admin account on first boot, then sign in and change the password (details):

BOOTSTRAP_ADMIN_EMAIL=owner@example.com
BOOTSTRAP_ADMIN_PASSWORD=<unique-initial-password-at-least-16-characters>

A TTS server you already run. Adds it as a shared provider on first boot; manage it afterwards in Settings → Admin → Providers (which address to use):

API_BASE=http://127.0.0.1:8880/v1
API_MODEL_NAME=kokoro
# API_KEY=<only if your server requires one>

External S3 instead of the embedded SeaweedFS (details):

USE_EMBEDDED_WEED_MINI=false
S3_BUCKET=your-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
S3_INTERNAL_ENDPOINT=https://s3.us-east-1.amazonaws.com
# Browsers download through the app. For presigned URLs instead, remove this
# line and set S3_PUBLIC_ENDPOINT to a public HTTPS endpoint.
S3_BROWSER_TRANSPORT=proxy
# Non-AWS providers that need path-style URLs: S3_FORCE_PATH_STYLE=true

An external worker instead of the embedded one. Set COMPUTE_WORKER_URL and the three shared values described in Compute Worker.

After signing in as an admin, Settings → Admin → Maintenance confirms the worker, storage and providers are healthy.

Generate migrations​

After changing the database schema, generate migration files for both SQLite and Postgres:

pnpm generate

This runs the Better Auth CLI to regenerate the auth table schemas, then drizzle-kit generate for both dialects. Running drizzle-kit generate directly skips the Better Auth step.

FilesOwner
packages/database/src/schema_sqlite.ts, schema_postgres.tsApp tables. Edit by hand and keep both dialects in sync.
packages/database/src/schema_auth_sqlite.ts, schema_auth_postgres.tsAuth tables, generated by Better Auth. Do not edit.
packages/database/migrations/{sqlite,postgres}/Generated SQL and meta/_journal.json for each dialect.

To apply migrations by hand, run pnpm migrate (Postgres when POSTGRES_URL is set, otherwise SQLite). See Database and Migrations.