Merge branch 'main' of https://github.com/richardr1126/OpenReader-WebUI
This commit is contained in:
commit
b41d09c94f
27 changed files with 2623 additions and 0 deletions
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: Acknowledgements
|
||||
---
|
||||
|
||||
This project is built with support from the following open-source projects and tools:
|
||||
|
||||
- [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M)
|
||||
- [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI)
|
||||
- [Better Auth](https://www.better-auth.com/)
|
||||
- [SQLite](https://www.sqlite.org/)
|
||||
- [PostgreSQL](https://www.postgresql.org/)
|
||||
- [SeaweedFS](https://github.com/seaweedfs/seaweedfs)
|
||||
- [OpenAI Whisper](https://github.com/openai/whisper)
|
||||
- [ffmpeg](https://ffmpeg.org)
|
||||
- [react-pdf](https://github.com/wojtekmaj/react-pdf)
|
||||
- [react-reader](https://github.com/happyr/react-reader)
|
||||
7
docs-site/versioned_docs/version-v4.0.1/about/license.md
Normal file
7
docs-site/versioned_docs/version-v4.0.1/about/license.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: License
|
||||
---
|
||||
|
||||
OpenReader is licensed under the MIT License.
|
||||
|
||||
- Repository license file: [LICENSE](https://github.com/richardr1126/openreader/blob/main/LICENSE)
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
title: Support and Contributing
|
||||
---
|
||||
|
||||
## Feature requests
|
||||
|
||||
Use [GitHub Discussions](https://github.com/richardr1126/openreader/discussions) for feature requests and ideas.
|
||||
|
||||
## Issues and support
|
||||
|
||||
If you encounter a bug, open an issue in [GitHub Issues](https://github.com/richardr1126/openreader/issues).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome.
|
||||
|
||||
- Fork the repository
|
||||
- Create your branch
|
||||
- Open a pull request with your changes
|
||||
149
docs-site/versioned_docs/version-v4.0.1/configure/admin-panel.md
Normal file
149
docs-site/versioned_docs/version-v4.0.1/configure/admin-panel.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
title: Admin Panel
|
||||
---
|
||||
|
||||
The admin panel lets a designated set of users manage **shared TTS providers** and **site-wide feature flags** directly from the Settings modal — without touching env vars or redeploying.
|
||||
|
||||
It is gated behind authentication, so you must have auth enabled to use it ([Auth](./auth)).
|
||||
|
||||
## Designating admins
|
||||
|
||||
Set `ADMIN_EMAILS` to a comma-separated list of emails:
|
||||
|
||||
```env
|
||||
AUTH_SECRET=... # required for auth
|
||||
BASE_URL=... # required for auth
|
||||
ADMIN_EMAILS=alice@example.com,bob@example.com
|
||||
```
|
||||
|
||||
On every session resolution the server compares the user's email against this list and writes `user.is_admin = true` (or `false` for emails removed from the list). No restart is required to demote — the next page load picks it up.
|
||||
|
||||
When the logged-in user is an admin, an **Admin** tab appears in **Settings → sidebar** with two sub-tabs:
|
||||
|
||||
- **Shared providers** — server-side TTS provider instances visible to all users.
|
||||
- **Site features** — runtime-editable replacements for what were previously build-time public env flags.
|
||||
|
||||
## Shared TTS providers
|
||||
|
||||
Each shared provider is one named instance bound to one of the four built-in provider types (`custom-openai`, `openai`, `replicate`, `deepinfra`). The admin form has:
|
||||
|
||||
| Field | Notes |
|
||||
| --- | --- |
|
||||
| **Slug** | URL-safe identifier exposed to users (e.g. `kokoro-prod`). Must not collide with a built-in id. Lowercase alphanumeric + hyphens. |
|
||||
| **Display name** | Shown in the user's provider dropdown, suffixed with "(shared)". |
|
||||
| **Provider type** | One of the four built-ins. Determines voice/model resolution. |
|
||||
| **Base URL** | Optional. Falls through to the provider type's default when blank. |
|
||||
| **API key** | Encrypted at rest with AES-256-GCM (key derived from `AUTH_SECRET` via scrypt). On edit, leave blank to keep the existing key. |
|
||||
| **Default model** | Optional. Used as the initial model when a user selects this provider. |
|
||||
| **Enabled** | Toggle to hide the provider from non-admin users without deleting it. |
|
||||
|
||||
When a non-admin user picks a shared provider in **Settings → TTS Provider**:
|
||||
|
||||
- The API key / base URL fields are hidden — those credentials never leave the server.
|
||||
- The TTS request still goes through the user's browser, but the server replaces the slug with the matching admin row's decrypted key and base URL before calling the upstream provider.
|
||||
- The user's per-request `x-openai-key` / `x-openai-base-url` headers are ignored for shared slugs.
|
||||
|
||||
Whether users can supply their own personal built-in provider keys is controlled by the site feature `restrictUserApiKeys`:
|
||||
|
||||
- `true`: users are restricted to shared providers only.
|
||||
- `false`: users may also use per-user BYOK credentials for built-in providers.
|
||||
|
||||
### Auto-seeded "default-openai"
|
||||
|
||||
On first boot, if `admin_providers` is empty and the legacy `API_KEY` env var is set, OpenReader creates a single shared provider with:
|
||||
|
||||
- slug `default-openai`, displayName `Default (from env)`, providerType `custom-openai`
|
||||
- baseUrl from `API_BASE`, apiKey from `API_KEY` (encrypted)
|
||||
- defaultModel set to `kokoro` (you can edit it in Admin → Shared providers)
|
||||
|
||||
After this seed runs, the legacy `API_KEY` / `API_BASE` env vars are no longer read by the TTS routes — the DB row is authoritative. You can rename, edit, disable, or delete this row like any other from the admin UI, and remove the env vars from your `.env` when convenient.
|
||||
|
||||
:::warning Upgrading from v2.2.0
|
||||
In v2.2.0 and earlier, `API_KEY` / `API_BASE` were read live by the TTS routes on every request. As of v3.0.0 they are **one-shot seeds** consumed only on the first boot where `admin_providers` is empty. After upgrading, boot the app once and confirm a `default-openai` row exists in **Settings → Admin → Shared providers** with the correct base URL. If it is missing or wrong (e.g. the env vars were not set on first boot, or the table was already non-empty from a pre-release), create or edit the shared provider manually — TTS will not fall back to the env vars.
|
||||
:::
|
||||
|
||||
## Site features
|
||||
|
||||
Runtime-editable settings, one row per key:
|
||||
|
||||
| Key | What it controls |
|
||||
| --- | --- |
|
||||
| `defaultTtsProvider` | Default provider id new users start with (built-in id or shared slug). |
|
||||
| `changelogFeedUrl` | Public changelog manifest URL used by the Settings modal changelog panel. |
|
||||
| `enableUserSignups` | Controls whether new accounts can be created. Existing accounts can still sign in when this is `false`. |
|
||||
| `restrictUserApiKeys` | Restrict user-supplied API keys/base URLs; when `true`, only admin shared providers are allowed. |
|
||||
| `enableTtsProvidersTab` | Whether the user-facing TTS Provider tab in Settings is shown. |
|
||||
| `showAllProviderModels` | When `false`, users are restricted to each provider's default model (shared provider `defaultModel` or built-in provider default). |
|
||||
| `enableAudiobookExport` | Show the audiobook export entry points on PDF/EPUB pages. |
|
||||
| `enableDocxConversion` | Accept .docx uploads (converted to PDF server-side). |
|
||||
| `enableDestructiveDeleteActions` | Show "Delete all data" buttons in the Documents tab (auth-disabled mode). |
|
||||
|
||||
Word-by-word highlighting and PDF layout parsing capability are controlled by compute-worker server env configuration, not an admin runtime flag.
|
||||
|
||||
Each row shows a source badge:
|
||||
|
||||
- **from seed** — the value was seeded on first boot (from `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH`).
|
||||
- **admin** — explicit admin override. Use **Reset** on the row to clear it back to built-in default behavior.
|
||||
- **default** — no seed/admin row exists; built-in default is active.
|
||||
|
||||
:::warning Security note for `restrictUserApiKeys`
|
||||
Turning `restrictUserApiKeys` off allows user-supplied API keys to flow through this server. Use this only for trusted/self-hosted deployments where that tradeoff is acceptable.
|
||||
:::
|
||||
|
||||
## Rate limiting
|
||||
|
||||
A dedicated **Rate limiting** group (within the same admin panel) collects the daily quotas, the PDF parsing throttle, and the upload size cap:
|
||||
|
||||
| Key | What it controls |
|
||||
| --- | --- |
|
||||
| `disableTtsRateLimit` | Disable the per-user/IP daily TTS character limits. When `false`, the daily-limit fields below it apply. |
|
||||
| `disableComputeRateLimit` | Disable per-user PDF parsing rate limiting. When `false`, the burst/sustained limit fields below it apply. |
|
||||
| `maxUploadMb` | Maximum size (MB) accepted for a single document upload. Enforced server-side and signed into the presigned S3 PUT. |
|
||||
|
||||
The **Disable TTS daily rate limiting** and **Disable PDF parsing rate limiting** toggles each reveal a collapsible group of numeric inputs when set to `false`:
|
||||
|
||||
- TTS: anonymous/authenticated per-user daily limits and anonymous/authenticated IP daily backstops.
|
||||
- PDF parsing: burst limit + window (seconds) and sustained limit + window (seconds). The sustained window doubles as a concurrency cap.
|
||||
|
||||
## TTS upstream
|
||||
|
||||
At the end of the **Site features** tab, a dedicated **TTS upstream** group controls server-side request and cache tuning (DB-backed runtime settings, not env vars):
|
||||
|
||||
| Key | What it controls |
|
||||
| --- | --- |
|
||||
| `ttsUpstreamMaxRetries` | Maximum retry attempts for upstream TTS 429/5xx responses. |
|
||||
| `ttsUpstreamTimeoutMs` | Upstream request timeout for OpenAI-compatible TTS calls. |
|
||||
| `ttsCacheMaxSizeBytes` | Maximum size of the in-memory TTS audio cache. |
|
||||
| `ttsCacheTtlMs` | Time-to-live for cached TTS audio buffers. |
|
||||
|
||||
In v4 these settings are admin-only and are no longer configurable through environment variables.
|
||||
|
||||
## Migrating off env vars
|
||||
|
||||
In v4, runtime site features are managed by admin settings and optional JSON seed. To minimize env surface area:
|
||||
|
||||
1. Deploy this version with your existing env values in place.
|
||||
2. Boot the app once. Open Settings → Admin and verify:
|
||||
- Seeded settings appear as **from seed** (if you supplied a runtime JSON seed).
|
||||
- A `default-openai` row exists in **Shared providers** (if you had `API_KEY` set).
|
||||
3. Remove any bootstrap env vars you no longer need from `.env`.
|
||||
4. Redeploy. Behavior is unchanged — the DB is now the source of truth.
|
||||
|
||||
You can keep `API_BASE` / `API_KEY` if you intentionally want bootstrap fallback behavior on empty provider tables.
|
||||
|
||||
## How keys are protected
|
||||
|
||||
- API keys are encrypted in the `admin_providers` table with AES-256-GCM. The encryption key is derived from `AUTH_SECRET` via `scrypt`.
|
||||
- The masked-list view (`GET /api/admin/providers`, used by the admin UI itself) returns `••••` + last-4 only — never plaintext or ciphertext.
|
||||
- The public list endpoint (`GET /api/tts/shared-providers`, called by every user's browser) returns only `{ slug, displayName, providerType, defaultModel }`. Keys and base URLs are never exposed to the client.
|
||||
- Non-admin users cannot enumerate admin providers' credentials or base URLs through any API.
|
||||
|
||||
:::danger Rotating `AUTH_SECRET` invalidates all stored admin provider keys
|
||||
Because the encryption key for `admin_providers` is derived from `AUTH_SECRET`, changing `AUTH_SECRET` makes every stored API key undecryptable. After rotating it, shared providers will fail to authenticate upstream until you re-enter each provider's API key in **Settings → Admin → Shared providers** (edit the row and paste the key again). There is no automated re-encryption path. If you must rotate `AUTH_SECRET`, plan to re-enter admin provider keys immediately afterward.
|
||||
:::
|
||||
|
||||
## Related
|
||||
|
||||
- [Auth](./auth) — required to use the admin panel.
|
||||
- [TTS Providers](./tts-providers) — built-in provider catalog and per-user behavior.
|
||||
- [Environment Variables](../reference/environment-variables) — `ADMIN_EMAILS`, provider bootstrap vars, and runtime JSON seed.
|
||||
64
docs-site/versioned_docs/version-v4.0.1/configure/auth.md
Normal file
64
docs-site/versioned_docs/version-v4.0.1/configure/auth.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: Auth
|
||||
---
|
||||
|
||||
This page covers application-level configuration for provider access and authentication.
|
||||
|
||||
## Auth behavior
|
||||
|
||||
- `BASE_URL` and `AUTH_SECRET` are required at startup in v4+.
|
||||
- Keep `AUTH_TRUSTED_ORIGINS` empty to trust only `BASE_URL`.
|
||||
- Anonymous auth sessions are disabled by default.
|
||||
- Set `USE_ANONYMOUS_AUTH_SESSIONS=true` to enable anonymous session flows.
|
||||
|
||||
## Runtime modes
|
||||
|
||||
OpenReader has two common runtime modes:
|
||||
|
||||
- **Auth enabled, non-admin user**: user account/session features are available, but no admin controls.
|
||||
- **Auth enabled, admin user**: full **Settings → Admin** access (shared providers + site features).
|
||||
|
||||
## Admin role
|
||||
|
||||
You can designate one or more users as admins via the `ADMIN_EMAILS` env var:
|
||||
|
||||
```env
|
||||
ADMIN_EMAILS=alice@example.com,bob@example.com
|
||||
```
|
||||
|
||||
Admins see a new **Admin** tab in **Settings** with two sub-tabs:
|
||||
|
||||
- **Shared TTS providers** — server-managed TTS provider instances with encrypted keys, visible to all users.
|
||||
- **Site features** — runtime overrides for what were previously build-time public env flags (including account signup availability, default TTS provider, audiobook export, etc.).
|
||||
|
||||
Admin assignment is reconciled on every session resolution, so removing an email from `ADMIN_EMAILS` demotes the user on next login without a restart. See [Admin Panel](./admin-panel) for the full reference.
|
||||
|
||||
## Route behavior
|
||||
|
||||
- `/` is a public landing/onboarding page and remains indexable.
|
||||
- `/app` is the protected app home (document list and uploader UI).
|
||||
- If a valid session exists (including anonymous), visiting `/` redirects to `/app`.
|
||||
- Protected app routes continue to require auth; when anonymous sessions are disabled and no session exists, users are redirected to `/signin`.
|
||||
|
||||
## Related docs
|
||||
|
||||
- For auth environment variables: [Environment Variables](../reference/environment-variables#auth-and-identity)
|
||||
- For admin role and shared TTS provider config: [Admin Panel](./admin-panel)
|
||||
- For TTS character limits and quota behavior: [TTS Rate Limiting](./tts-rate-limiting)
|
||||
- For provider-specific guidance: [TTS Providers](./tts-providers)
|
||||
- For storage/S3/SeaweedFS behavior: [Object / Blob Storage](./object-blob-storage)
|
||||
- For database mode: [Database](./database)
|
||||
- For migration behavior and commands: [Migrations](./migrations)
|
||||
|
||||
## Sync notes
|
||||
|
||||
### Auth enabled
|
||||
|
||||
- Settings and reading progress are saved to the server.
|
||||
- Updates are not instant push-based sync; they use normal client polling/refresh behavior.
|
||||
- If two devices change the same item around the same time, the newest update wins.
|
||||
|
||||
## Claim modal note
|
||||
|
||||
- You may still see old anonymous settings/progress available to claim from older deployments.
|
||||
- Legacy `unclaimed` data is only surfaced through the claim flow; normal authenticated routes are scoped to your current user id.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: Database
|
||||
---
|
||||
|
||||
This page covers database mode selection for OpenReader.
|
||||
|
||||
## Scope of this page
|
||||
|
||||
- Focus: SQL metadata, state, and relational tables.
|
||||
- Not covered here: object key layout and blob transport details (see [Object / Blob Storage](./object-blob-storage)).
|
||||
|
||||
## Database mode
|
||||
|
||||
- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
|
||||
- Postgres: enabled when `POSTGRES_URL` is set; recommended for production/distributed deployments.
|
||||
|
||||
## What the database stores
|
||||
|
||||
- Document and audiobook metadata/state used by server routes.
|
||||
- Auth/session tables (`user`, `session`, `account`, `verification`) when auth is enabled — schema is auto-generated by Better Auth.
|
||||
- TTS character usage counters (`user_tts_chars`) for daily rate limiting (when enabled).
|
||||
- User settings preferences (`user_preferences`) when auth is enabled.
|
||||
- User reading progress (`user_document_progress`) when auth is enabled.
|
||||
- Document preview job/asset metadata (`document_previews`) for server-side PDF/EPUB thumbnails.
|
||||
- TTS segment metadata (`tts_segments`) for server-side playback caching:
|
||||
- Segment identity + settings hash
|
||||
- Audio object key and duration
|
||||
- Optional alignment payload for word highlighting
|
||||
- Status/error state
|
||||
- Text fingerprint/hash (plaintext segment text is not stored)
|
||||
|
||||
App-specific tables are manually maintained in Drizzle schema files, while auth tables are generated by the Better Auth CLI. Both are migrated together via Drizzle. See [Migrations](./migrations) for details.
|
||||
|
||||
## What the database does not store
|
||||
|
||||
- Raw document file bytes
|
||||
- Audiobook audio bytes
|
||||
- TTS segment audio bytes
|
||||
- Generated preview image bytes
|
||||
|
||||
Those payloads live in object storage. SQL stores the metadata, references, and status.
|
||||
|
||||
## Related variables
|
||||
|
||||
- `POSTGRES_URL`
|
||||
|
||||
For database variable behavior, see [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Migrations](./migrations)
|
||||
- [Object / Blob Storage](./object-blob-storage)
|
||||
- [Auth](./auth)
|
||||
|
||||
## State sync summary
|
||||
|
||||
- Settings and reading progress are stored in SQL and synced from the app.
|
||||
- Sync is currently request-based (not realtime push invalidation).
|
||||
150
docs-site/versioned_docs/version-v4.0.1/configure/migrations.md
Normal file
150
docs-site/versioned_docs/version-v4.0.1/configure/migrations.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
title: Migrations
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
This page covers migration behavior for both database schema and storage data in OpenReader.
|
||||
|
||||
## Startup migration behavior
|
||||
|
||||
By default, the shared entrypoint runs migrations automatically before app startup in:
|
||||
|
||||
- Docker container startup
|
||||
- `pnpm dev`
|
||||
- `pnpm start`
|
||||
|
||||
Startup migration phases:
|
||||
|
||||
- DB schema migrations (`pnpm migrate`)
|
||||
- Storage/data migration (`pnpm migrate-fs`) for legacy filesystem content into S3 + DB rows
|
||||
|
||||
:::info
|
||||
In most setups, you do not need to run migration commands manually because startup handles this automatically.
|
||||
:::
|
||||
|
||||
### Schema history
|
||||
|
||||
Migrations are applied in order. All of the following ship in v3.0.0; an instance upgrading from v2.2.0 applies `0001`–`0004` in a single startup pass.
|
||||
|
||||
| Migration | Dialects | What it does |
|
||||
| --- | --- | --- |
|
||||
| `0001_tts_segments` | SQLite + Postgres | Creates the original single-table `tts_segments` used by server-side TTS segment caching. |
|
||||
| `0002_add_segment_key_to_tts_segments` | SQLite + Postgres | Adds the `segment_key` column to `tts_segments` for stable locator-independent segment identity. |
|
||||
| `0003_tts_segments_v2_split` | SQLite + Postgres | Replaces `tts_segments` with a normalized two-table model: `tts_segment_entries` (one row per document segment + locator identity) and `tts_segment_variants` (one row per settings combination, holding the cached audio key, status, and alignment). Drops the original `tts_segments` table — no released build (v2.2.0 or earlier) ever populated it, so there is no production data to migrate. |
|
||||
| `0004_admin_panel` | SQLite + Postgres | Creates `admin_providers` (encrypted shared TTS provider rows) and `admin_settings` (runtime site-feature config), and adds the `is_admin` column to the `user` table. Backs the [Admin Panel](./admin-panel). |
|
||||
|
||||
To skip automatic startup migrations:
|
||||
|
||||
- Set `RUN_DRIZZLE_MIGRATIONS=false`
|
||||
- Set `RUN_FS_MIGRATIONS=false`
|
||||
|
||||
:::warning
|
||||
If you disable startup migrations, ensure your deployment process runs migrations before serving traffic.
|
||||
:::
|
||||
|
||||
## Apply migrations
|
||||
|
||||
In most cases, you do not need manual migration commands because startup runs migrations automatically.
|
||||
|
||||
`pnpm migrate` applies migrations for one database target:
|
||||
|
||||
- Postgres when `POSTGRES_URL` is set
|
||||
- SQLite when `POSTGRES_URL` is unset
|
||||
|
||||
You can always override the target explicitly with `--config`.
|
||||
|
||||
<Tabs groupId="apply-migration-commands">
|
||||
<TabItem value="project-scripts" label="Project Scripts" default>
|
||||
|
||||
```bash
|
||||
# Run pending migrations for one target:
|
||||
# - Postgres if POSTGRES_URL is set
|
||||
# - SQLite if POSTGRES_URL is unset
|
||||
pnpm migrate
|
||||
|
||||
# Run storage migration (filesystem -> S3 + DB)
|
||||
pnpm migrate-fs
|
||||
|
||||
# Dry-run storage migration without uploading/deleting
|
||||
pnpm migrate-fs:dry-run
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||
|
||||
```bash
|
||||
# Migrate SQLite
|
||||
pnpm exec drizzle-kit migrate --config drizzle.config.sqlite.ts
|
||||
|
||||
# Migrate Postgres
|
||||
pnpm exec drizzle-kit migrate --config drizzle.config.pg.ts
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Generate migrations
|
||||
|
||||
`pnpm generate` is a two-phase script for contributors and schema changes:
|
||||
|
||||
1. **Better Auth schema generation** — runs the Better Auth CLI twice (once for SQLite, once for Postgres) to produce auto-generated Drizzle schema files for auth tables (`user`, `session`, `account`, `verification`).
|
||||
2. **Drizzle migration generation** — runs `drizzle-kit generate` for both `drizzle.config.sqlite.ts` and `drizzle.config.pg.ts`, producing SQL migration files from all schema files (app + auth).
|
||||
|
||||
:::note
|
||||
Most users do not need to run `pnpm generate`. Use it when contributing or when you have changed Drizzle schema files and need new migration files.
|
||||
:::
|
||||
|
||||
### Schema ownership
|
||||
|
||||
Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
|
||||
|
||||
- `src/db/schema_auth_sqlite.ts`
|
||||
- `src/db/schema_auth_postgres.ts`
|
||||
|
||||
App-specific tables are manually maintained in the standard Drizzle schema files:
|
||||
|
||||
- `src/db/schema_sqlite.ts`
|
||||
- `src/db/schema_postgres.ts`
|
||||
|
||||
Both sets of schema files are included in the Drizzle configs, so `drizzle-kit generate` and `drizzle-kit migrate` handle all tables together.
|
||||
|
||||
When app schema changes (for example `tts_segments`), keep these in sync:
|
||||
|
||||
- `src/db/schema_sqlite.ts`
|
||||
- `src/db/schema_postgres.ts`
|
||||
- `drizzle/sqlite/*.sql` + `drizzle/sqlite/meta/_journal.json`
|
||||
- `drizzle/postgres/*.sql` + `drizzle/postgres/meta/_journal.json`
|
||||
|
||||
<Tabs groupId="generate-migration-commands">
|
||||
<TabItem value="project-script" label="Project Script" default>
|
||||
|
||||
```bash
|
||||
# Full pipeline: Better Auth CLI + Drizzle generate (both dialects)
|
||||
pnpm generate
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||
|
||||
```bash
|
||||
# Generate SQLite migrations only (skips Better Auth CLI)
|
||||
pnpm exec drizzle-kit generate --config drizzle.config.sqlite.ts
|
||||
|
||||
# Generate Postgres migrations only (skips Better Auth CLI)
|
||||
pnpm exec drizzle-kit generate --config drizzle.config.pg.ts
|
||||
```
|
||||
|
||||
:::warning
|
||||
Running `drizzle-kit generate` directly skips the Better Auth CLI step. If auth schema has changed upstream (e.g. after a Better Auth version bump), run `pnpm generate` instead to regenerate the auth schema files first.
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Database](./database)
|
||||
- [Object / Blob Storage](./object-blob-storage)
|
||||
- [Migration Environment Variables](../reference/environment-variables#migration-controls)
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
---
|
||||
title: Object / Blob Storage
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
This page documents storage backends, blob upload routing, and core Docker mount behavior.
|
||||
|
||||
## Scope of this page
|
||||
|
||||
- Focus: object/blob backends, keyspaces, upload/read paths, and storage debugging.
|
||||
- Not covered here: relational metadata tables and SQL state modeling (see [Database](./database)).
|
||||
|
||||
## Storage backends
|
||||
|
||||
- Embedded (default): embedded SeaweedFS (`weed mini`) blob storage.
|
||||
- External: external S3-compatible object storage.
|
||||
|
||||
Metadata database mode (SQLite vs Postgres) is configured separately in [Database](./database).
|
||||
|
||||
:::warning SeaweedFS Compatibility Note (April 16, 2026)
|
||||
OpenReader currently pins embedded SeaweedFS to `4.18` in CI and Docker builds.
|
||||
`4.19` introduced intermittent `InternalError` responses on S3 `PutObject` in our upload flow.
|
||||
:::
|
||||
|
||||
Storage variables are documented in [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
|
||||
|
||||
## Ports
|
||||
|
||||
- `3003`: OpenReader app and API routes
|
||||
- `8333`: Embedded SeaweedFS S3 endpoint for direct browser blob access
|
||||
|
||||
:::info
|
||||
`8333` is only needed for direct browser presigned access to embedded SeaweedFS.
|
||||
:::
|
||||
|
||||
## Upload behavior
|
||||
|
||||
- Primary path: browser uploads to presigned URL from `/api/documents/blob/upload/presign`.
|
||||
- Fallback path: `/api/documents/blob/upload/fallback` when direct upload fails/unreachable.
|
||||
- Read/download path: blob/content serving route `/api/documents/blob` (not the upload fallback route).
|
||||
- Preview path: `/api/documents/blob/preview` (returns `202` while a preview is generating; serves/redirects when ready).
|
||||
|
||||
## Document previews
|
||||
|
||||
- PDF/EPUB previews are generated server-side and stored in object storage under `document_previews_v1`.
|
||||
- Preview generation is triggered on upload registration and also backfills on first preview request for older docs.
|
||||
- Preview artifacts are temporary-cache friendly and can be regenerated from the source document blob.
|
||||
|
||||
## FS / Volume Mounts
|
||||
|
||||
### App data mount
|
||||
|
||||
- Target: `/app/docstore`
|
||||
- Recommended: yes, for persistence
|
||||
- Purpose: persists SeaweedFS blob data, SQLite metadata DB, migrations, and local runtime temp state
|
||||
- Mount string: `-v openreader_docstore:/app/docstore`
|
||||
|
||||
### Library source mount (optional)
|
||||
|
||||
- Target: `/app/docstore/library`
|
||||
- Recommended: optional, use read-only (`:ro`)
|
||||
- Purpose: exposes host files as a source for server library import
|
||||
- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
|
||||
- Details: [Server Library Import](./server-library-import)
|
||||
|
||||
## Private blob endpoint mode
|
||||
|
||||
If `8333` is not published externally:
|
||||
|
||||
- Document uploads still work through upload fallback proxy
|
||||
- Reads/snippets continue through app API routes
|
||||
- Direct presigned browser upload/download to embedded endpoint is unavailable
|
||||
|
||||
:::warning
|
||||
Without `8333`, expect higher app-server traffic because uploads/downloads go through API routes instead of direct object endpoint access.
|
||||
:::
|
||||
|
||||
## Audiobook Storage Debug Commands
|
||||
|
||||
Audiobook assets are stored in object storage under the `audiobooks_v1` keyspace. Use these commands to inspect and download objects for debugging.
|
||||
|
||||
<Tabs groupId="audiobook-storage-access-cli">
|
||||
<TabItem value="aws-s3" label="AWS S3" default>
|
||||
|
||||
```bash
|
||||
# List all audiobook objects
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive
|
||||
|
||||
# Filter to one book id (replace <book-id>)
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive | grep "<book-id>-audiobook/"
|
||||
|
||||
# Download one object by full key
|
||||
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/<path>/<file>.m4b" "./audiobook.m4b"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="s3-compatible" label="Embedded / MinIO / R2 / etc">
|
||||
|
||||
```bash
|
||||
# List all audiobook objects
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive --endpoint-url "$S3_ENDPOINT"
|
||||
|
||||
# Filter to one book id (replace <book-id>)
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive --endpoint-url "$S3_ENDPOINT" | grep "<book-id>-audiobook/"
|
||||
|
||||
# Download one object by full key
|
||||
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/<path>/<file>.m4b" "./audiobook.m4b" --endpoint-url "$S3_ENDPOINT"
|
||||
```
|
||||
|
||||
Embedded default example: `S3_ENDPOINT=http://127.0.0.1:8333` (or your mapped host/port).
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## TTS Segment Storage
|
||||
|
||||
Server-side TTS segment audio is stored in object storage under the `tts_segments_v1` keyspace.
|
||||
|
||||
Typical key layout:
|
||||
|
||||
- `${S3_PREFIX}/tts_segments_v1/users/<url-encoded-user-id>/docs/<document-id>/<document-version>/<settings-hash>/<segment-id>.mp3`
|
||||
- `${S3_PREFIX}/tts_segments_v1/ns/<test-namespace>/users/<url-encoded-user-id>/docs/...` (test namespace mode)
|
||||
|
||||
Notes:
|
||||
|
||||
- For the corresponding SQL metadata model (`tts_segments`), see [Database](./database).
|
||||
|
||||
## Account Deletion Cleanup
|
||||
|
||||
Account deletion performs best-effort object cleanup:
|
||||
|
||||
- Document blobs + preview artifacts
|
||||
- Audiobook blobs
|
||||
- TTS segment blobs under `tts_segments_v1`
|
||||
|
||||
If object deletion fails, account deletion still proceeds and orphaned objects may require manual cleanup.
|
||||
|
||||
## TTS Segment Storage Debug Commands
|
||||
|
||||
Use these commands to inspect segment objects.
|
||||
|
||||
<Tabs groupId="tts-segment-storage-access-cli">
|
||||
<TabItem value="aws-s3" label="AWS S3" default>
|
||||
|
||||
```bash
|
||||
# List all TTS segment objects
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive
|
||||
|
||||
# Filter to one document id (replace <document-id>)
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive | grep "/docs/<document-id>/"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="s3-compatible" label="Embedded / MinIO / R2 / etc">
|
||||
|
||||
```bash
|
||||
# List all TTS segment objects
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive --endpoint-url "$S3_ENDPOINT"
|
||||
|
||||
# Filter to one document id (replace <document-id>)
|
||||
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive --endpoint-url "$S3_ENDPOINT" | grep "/docs/<document-id>/"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
title: Server Library Import
|
||||
---
|
||||
|
||||
This page documents how server library import works and how to configure it.
|
||||
|
||||
## What it does
|
||||
|
||||
Server library import lets you browse files from one or more server directories and import selected files into OpenReader.
|
||||
|
||||
- Import is user-driven via a selection modal
|
||||
- Only selected files are imported
|
||||
- Imported files become normal OpenReader documents
|
||||
|
||||
## FS / Volume Mounts
|
||||
|
||||
### App data mount
|
||||
|
||||
- Target: `/app/docstore`
|
||||
- Recommended: yes, for persistence
|
||||
- Purpose: stores app runtime data, metadata DB, and embedded storage state
|
||||
- Mount string: `-v openreader_docstore:/app/docstore`
|
||||
|
||||
### Library source mount
|
||||
|
||||
- Target: `/app/docstore/library`
|
||||
- Recommended: yes for this feature, use read-only (`:ro`)
|
||||
- Purpose: exposes host files as import candidates in Server Library Import
|
||||
- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
|
||||
|
||||
## Import flow
|
||||
|
||||
1. Open **Settings -> Documents -> Server Library Import**.
|
||||
2. Select files in the modal.
|
||||
3. Click **Import**.
|
||||
|
||||
Selected files are fetched from the server library endpoint and imported into OpenReader storage.
|
||||
|
||||
:::warning Shared Library Roots
|
||||
Library roots are configured at the server level (not per-user). Any user who can access Server Library Import can browse/import from the same configured roots.
|
||||
|
||||
Imported documents are still saved to the importing user's document scope.
|
||||
:::
|
||||
|
||||
## Supported file types
|
||||
|
||||
- `.pdf`
|
||||
- `.epub`
|
||||
- `.html`, `.htm`
|
||||
- `.txt`
|
||||
- `.md`, `.mdown`, `.markdown`
|
||||
|
||||
## Optional: Configure Library Roots
|
||||
|
||||
You only need this when the default mounted path is not what you want.
|
||||
|
||||
By default, OpenReader uses `docstore/library` as the import root. You can override that with environment variables:
|
||||
|
||||
- `IMPORT_LIBRARY_DIRS` (takes precedence): multiple roots separated by comma, colon, or semicolon
|
||||
- `IMPORT_LIBRARY_DIR`: single root
|
||||
|
||||
See [Environment Variables](../reference/environment-variables#library-import) for variable details.
|
||||
|
||||
## Notes
|
||||
|
||||
- Library listing is capped per request (up to 10,000 files).
|
||||
- When auth is enabled, library import endpoints require a valid session.
|
||||
- The mounted library is a source; removing it does not delete already imported documents.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: DeepInfra
|
||||
---
|
||||
|
||||
Use DeepInfra's hosted TTS models as your provider.
|
||||
|
||||
## Setup
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `deepinfra`.
|
||||
2. Keep base URL as `https://api.deepinfra.com/v1/openai`.
|
||||
3. Enter your API key.
|
||||
4. Set your preferred default model/voice.
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_BASE=https://api.deepinfra.com/v1/openai
|
||||
API_KEY=your-deepinfra-key
|
||||
```
|
||||
|
||||
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
|
||||
|
||||
1. Set provider to `Deepinfra`.
|
||||
2. The base URL is pre-filled, no changes needed.
|
||||
3. Enter your `API_KEY`.
|
||||
4. Choose a model and voice.
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
## Notes
|
||||
|
||||
- Available models include `hexgrad/Kokoro-82M` and `canopylabs/orpheus-3b-0.1-ft`.
|
||||
- Without an API key, only the free-tier model (`hexgrad/Kokoro-82M`) is shown in the dropdown.
|
||||
- TTS requests are sent from the server, not the browser. The API key is never exposed to clients.
|
||||
|
||||
## References
|
||||
|
||||
- [DeepInfra TTS models](https://deepinfra.com/models/text-to-speech)
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: KittenTTS-FastAPI
|
||||
---
|
||||
|
||||
Run [KittenTTS-FastAPI](https://github.com/richardr1126/KittenTTS-FastAPI) locally and connect it to OpenReader using the `Custom OpenAI-Like` provider. Lightweight and CPU-friendly.
|
||||
|
||||
## Run KittenTTS
|
||||
|
||||
```bash
|
||||
docker run -it --rm \
|
||||
--name kittentts-fastapi \
|
||||
-e KITTEN_MODEL_REPO_ID="KittenML/kitten-tts-nano-0.8-fp32" \
|
||||
-p 8005:8005 \
|
||||
ghcr.io/richardr1126/kittentts-fastapi-cpu
|
||||
```
|
||||
|
||||
## Connect to OpenReader
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `custom-openai`.
|
||||
2. Set base URL to your KittenTTS endpoint (e.g. `http://kittentts-fastapi:8005/v1`).
|
||||
3. Leave API key blank unless required by your deployment.
|
||||
4. Set default model to `kitten-tts` (or your backend model id).
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_BASE=http://kittentts-fastapi:8005/v1
|
||||
```
|
||||
|
||||
> Use `kittentts-fastapi` if that's the container name, or `host.docker.internal` if not.
|
||||
|
||||
**Or in-app via Settings → TTS Provider:**
|
||||
|
||||
1. Set provider to `Custom OpenAI-Like`.
|
||||
2. Set `API_BASE` to your KittenTTS endpoint (e.g. `http://kittentts-fastapi:8005/v1`).
|
||||
3. Leave `API_KEY` blank unless your deployment requires one.
|
||||
4. Choose model `kitten-tts` (or the model your deployment exposes).
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
## References
|
||||
|
||||
- [richardr1126/KittenTTS-FastAPI](https://github.com/richardr1126/KittenTTS-FastAPI)
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
title: Kokoro-FastAPI
|
||||
---
|
||||
|
||||
Run [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI) locally and connect it to OpenReader using the `Custom OpenAI-Like` provider.
|
||||
|
||||
:::warning
|
||||
For Kokoro issues and support, use the upstream repository: [remsky/Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI).
|
||||
:::
|
||||
|
||||
## Run Kokoro
|
||||
|
||||
**CPU:**
|
||||
|
||||
```bash
|
||||
docker run --name kokoro-tts \
|
||||
--restart unless-stopped \
|
||||
-d \
|
||||
-p 8880:8880 \
|
||||
-e ONNX_NUM_THREADS=8 \
|
||||
-e ONNX_INTER_OP_THREADS=4 \
|
||||
-e ONNX_EXECUTION_MODE=parallel \
|
||||
-e ONNX_OPTIMIZATION_LEVEL=all \
|
||||
-e ONNX_MEMORY_PATTERN=true \
|
||||
-e ONNX_ARENA_EXTEND_STRATEGY=kNextPowerOfTwo \
|
||||
-e API_LOG_LEVEL=DEBUG \
|
||||
ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
|
||||
```
|
||||
|
||||
**GPU (NVIDIA):**
|
||||
|
||||
```bash
|
||||
docker run --name kokoro-tts \
|
||||
--restart unless-stopped \
|
||||
-d \
|
||||
--gpus all \
|
||||
--user 1001:1001 \
|
||||
-p 8880:8880 \
|
||||
-e USE_GPU=true \
|
||||
-e PYTHONUNBUFFERED=1 \
|
||||
-e API_LOG_LEVEL=DEBUG \
|
||||
ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.4
|
||||
```
|
||||
|
||||
## Connect to OpenReader
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `custom-openai`.
|
||||
2. Set base URL to your Kokoro endpoint (e.g. `http://kokoro-tts:8880/v1`).
|
||||
3. Leave API key blank unless required by your deployment.
|
||||
4. Set default model to `Kokoro`.
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_BASE=http://kokoro-tts:8880/v1
|
||||
```
|
||||
|
||||
> Use `kokoro-tts` if that's the container name, or `host.docker.internal` if not.
|
||||
|
||||
**Or in-app via Settings → TTS Provider:**
|
||||
|
||||
1. Set provider to `Custom OpenAI-Like`.
|
||||
2. Set `API_BASE` to your Kokoro endpoint (e.g. `http://kokoro-tts:8880/v1`).
|
||||
3. Leave `API_KEY` blank unless your deployment requires one.
|
||||
4. Choose model `Kokoro`.
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
## References
|
||||
|
||||
- [remsky/Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI)
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: OpenAI
|
||||
---
|
||||
|
||||
Use the OpenAI TTS API as your provider.
|
||||
|
||||
## Setup
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `openai`.
|
||||
2. Keep base URL as `https://api.openai.com/v1`.
|
||||
3. Enter your API key.
|
||||
4. Set your preferred default model/voice.
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_BASE=https://api.openai.com/v1
|
||||
API_KEY=sk-...
|
||||
```
|
||||
|
||||
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
|
||||
|
||||
1. Set provider to `OpenAI`.
|
||||
2. The base URL is pre-filled, no changes needed.
|
||||
3. Enter your `API_KEY`.
|
||||
4. Choose a model and voice.
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
## Notes
|
||||
|
||||
- Models: `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`
|
||||
- TTS requests are sent from the server, not the browser. The API key is never exposed to clients.
|
||||
|
||||
## References
|
||||
|
||||
- [OpenAI TTS pricing](https://platform.openai.com/docs/pricing#transcription-and-speech)
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: Orpheus-FastAPI
|
||||
---
|
||||
|
||||
Run [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI) locally and connect it to OpenReader using the `Custom OpenAI-Like` provider.
|
||||
|
||||
## Run Orpheus
|
||||
|
||||
Refer to the upstream repository for Docker instructions: [Lex-au/Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI).
|
||||
|
||||
## Connect to OpenReader
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `custom-openai`.
|
||||
2. Set base URL to your Orpheus endpoint (e.g. `http://orpheus:8000/v1`).
|
||||
3. Leave API key blank unless required by your deployment.
|
||||
4. Set default model to `Orpheus` (or your backend model id).
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_BASE=http://orpheus:8000/v1
|
||||
```
|
||||
|
||||
> Use the container name if that's how it's named, or `host.docker.internal` if not.
|
||||
|
||||
**Or in-app via Settings → TTS Provider:**
|
||||
|
||||
1. Set provider to `Custom OpenAI-Like`.
|
||||
2. Set `API_BASE` to your Orpheus endpoint (e.g. `http://orpheus:8000/v1`).
|
||||
3. Leave `API_KEY` blank unless your deployment requires one.
|
||||
4. Choose model `Orpheus` (or the model your deployment exposes).
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
## References
|
||||
|
||||
- [Lex-au/Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI)
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: Other
|
||||
---
|
||||
|
||||
Use any OpenAI-compatible TTS service with OpenReader, including self-hosted servers not covered by a dedicated guide.
|
||||
|
||||
## Requirements
|
||||
|
||||
Your service must expose these endpoints:
|
||||
|
||||
- `GET /v1/audio/voices`
|
||||
- `POST /v1/audio/speech`
|
||||
|
||||
Known compatible implementations: [Kokoro-FastAPI](./kokoro-fastapi), [KittenTTS-FastAPI](./kitten-tts-fastapi), [Orpheus-FastAPI](./orpheus-fastapi).
|
||||
|
||||
## Setup
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `custom-openai`.
|
||||
2. Set `API_BASE` to your service base URL (typically ending in `/v1`).
|
||||
3. Set API key if your service requires authentication.
|
||||
4. Set a default model/voice supported by your backend.
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_BASE=http://your-tts-server/v1
|
||||
API_KEY=optional-key-if-required
|
||||
```
|
||||
|
||||
**Or in-app via Settings → TTS Provider:**
|
||||
|
||||
1. Set provider to `Custom OpenAI-Like`.
|
||||
2. Set `API_BASE` to your service's base URL (typically ending in `/v1`).
|
||||
3. Set `API_KEY` if your service requires authentication.
|
||||
4. Choose a model and voice supported by your backend.
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
:::warning TTS requests are server-side
|
||||
`API_BASE` must be reachable from the **Next.js server**, not just the browser. In Docker, use container names or `host.docker.internal`.
|
||||
:::
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If voices don't load, check that `/v1/audio/voices` is reachable from the server and returns a valid response shape.
|
||||
|
||||
## References
|
||||
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
title: Replicate
|
||||
---
|
||||
|
||||
Use Replicate's hosted TTS models as your provider.
|
||||
|
||||
## Setup
|
||||
|
||||
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||
|
||||
1. Add a shared provider with type `replicate`.
|
||||
2. Enter your API key.
|
||||
3. Set default model to:
|
||||
`alphanumericuser/kokoro-82m:89b6fa84e4fa2dd6bd3a96be3e1f12827a3516c9fda8fddbac7a0be131c9a6f5` (or your preferred model).
|
||||
|
||||
**Legacy bootstrap seed (optional, first boot only):**
|
||||
|
||||
```env
|
||||
API_KEY=r8_...
|
||||
```
|
||||
|
||||
Then update the shared provider's **Default model** in **Settings → Admin → Shared providers**.
|
||||
|
||||
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
|
||||
|
||||
1. Set provider to `Replicate`.
|
||||
2. Enter your `API_KEY`.
|
||||
3. Choose a model and voice.
|
||||
|
||||
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||
|
||||
## Notes
|
||||
|
||||
- Built-in Replicate models:
|
||||
- `alphanumericuser/kokoro-82m:89b6fa84e4fa2dd6bd3a96be3e1f12827a3516c9fda8fddbac7a0be131c9a6f5`
|
||||
- `google/gemini-3.1-flash-tts`
|
||||
- `minimax/speech-2.8-turbo`
|
||||
- `qwen/qwen3-tts`
|
||||
- `inworld/tts-1.5-mini`
|
||||
- You can also choose `Other` and enter any Replicate model ID (for example `owner/model-name` or `owner/model-name:version`).
|
||||
- Native model speed is not available on all Replicate models; OpenReader hides/disables native speed controls where unsupported.
|
||||
- TTS requests are sent from the server, not the browser. The API key is never exposed to clients.
|
||||
|
||||
## References
|
||||
|
||||
- [Replicate](https://replicate.com/explore)
|
||||
- [TTS Providers](../tts-providers)
|
||||
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
title: TTS Providers
|
||||
---
|
||||
|
||||
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. There are three places provider configuration can live:
|
||||
|
||||
**Admin-managed shared providers** (Settings > Admin > Shared providers): DB-backed instances configured by an admin and visible to all users. Keys are encrypted at rest and never exposed to the client. Available only when [auth is enabled](./auth) and your account is in `ADMIN_EMAILS`. See [Admin Panel](./admin-panel).
|
||||
|
||||
**Per-user Settings modal** (Settings > TTS Provider): provider + API key stored in the user's browser and sent with every TTS request. This path is available only when the admin/runtime setting `restrictUserApiKeys=false`.
|
||||
|
||||
**Environment variables**: `API_KEY` and `API_BASE` exist as a one-shot first-boot seed that auto-creates a `default-openai` admin shared provider. After the first boot they are no longer read by the running app.
|
||||
|
||||
:::tip
|
||||
If you're running a private/self-hosted instance and want per-user BYOK behavior, turn off **Settings → Admin → Site features → Restrict user API keys**. For first-boot automation, set `runtimeConfig.restrictUserApiKeys=false` in `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH`.
|
||||
:::
|
||||
|
||||
## Providers
|
||||
|
||||
- **OpenAI**: Cloud. Base URL pre-filled (`https://api.openai.com/v1`). API key required.
|
||||
- **Replicate**: Cloud. Base URL managed internally by OpenReader. API key required.
|
||||
- **DeepInfra**: Cloud. Base URL pre-filled (`https://api.deepinfra.com/v1/openai`). API key required.
|
||||
- **Custom OpenAI-Like**: Self-hosted or any custom endpoint. `API_BASE` must be set manually (typically ending in `/v1`). API key optional.
|
||||
|
||||
For `OpenAI`, `DeepInfra`, and `Replicate` you only need to supply an API key. For `Custom OpenAI-Like` you must also set `API_BASE`.
|
||||
|
||||
## Built-in model catalogs
|
||||
|
||||
- **Replicate** models: `alphanumericuser/kokoro-82m`, `google/gemini-3.1-flash-tts`, `minimax/speech-2.8-turbo`, `qwen/qwen3-tts`, `inworld/tts-1.5-mini` (or choose `Other` and enter any Replicate model ID, such as `owner/model` or `owner/model:version`)
|
||||
- **OpenAI** models: `tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`
|
||||
- **DeepInfra** models: includes `hexgrad/Kokoro-82M` and additional hosted models (depending on API key / feature flags)
|
||||
|
||||
## Custom provider requirements
|
||||
|
||||
Self-hosted or custom providers must expose OpenAI-compatible audio endpoints:
|
||||
|
||||
- `GET /v1/audio/voices`
|
||||
- `POST /v1/audio/speech`
|
||||
|
||||
:::warning TTS requests are server-side
|
||||
TTS requests originate from the **Next.js server**, not the browser. `API_BASE` must be reachable from the server runtime. In Docker, use container names or `host.docker.internal` rather than `localhost`.
|
||||
:::
|
||||
|
||||
## Provider guides
|
||||
|
||||
- [Kokoro-FastAPI](./tts-provider-guides/kokoro-fastapi)
|
||||
- [KittenTTS-FastAPI](./tts-provider-guides/kitten-tts-fastapi)
|
||||
- [Orpheus-FastAPI](./tts-provider-guides/orpheus-fastapi)
|
||||
- [Replicate](./tts-provider-guides/replicate)
|
||||
- [DeepInfra](./tts-provider-guides/deepinfra)
|
||||
- [OpenAI](./tts-provider-guides/openai)
|
||||
- [Other](./tts-provider-guides/other)
|
||||
|
||||
## Related
|
||||
|
||||
- [Admin Panel](./admin-panel) — DB-backed shared providers with encrypted keys
|
||||
- [TTS Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
- [TTS Rate Limiting](./tts-rate-limiting)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
title: TTS Rate Limiting
|
||||
---
|
||||
|
||||
This page explains OpenReader's TTS character rate limiting controls.
|
||||
|
||||
## Overview
|
||||
|
||||
- TTS rate limiting is disabled by default.
|
||||
- Primary control is **Settings → Admin → Site features → Disable TTS daily rate limiting**.
|
||||
- Limits are enforced per day in UTC.
|
||||
- Enforcement applies only when auth is enabled.
|
||||
|
||||
## How enforcement works
|
||||
|
||||
When enabled, OpenReader enforces:
|
||||
|
||||
- Per-user daily character limits.
|
||||
- IP backstop daily character limits.
|
||||
- Anonymous device backstop tracking (cookie-based) to reduce limit resets.
|
||||
|
||||
If a request exceeds the active limit, the TTS API returns `429` with reset metadata for the next UTC day.
|
||||
|
||||
## Required auth behavior
|
||||
|
||||
- Auth must be enabled (`BASE_URL` + `AUTH_SECRET`) for TTS char limits to apply.
|
||||
- If auth is disabled, TTS character limits are effectively unlimited.
|
||||
- `DISABLE_AUTH_RATE_LIMIT` only affects Better Auth's own request throttling.
|
||||
- `DISABLE_AUTH_RATE_LIMIT` does not disable TTS character limits.
|
||||
|
||||
## Runtime config
|
||||
|
||||
- `disableTtsRateLimit` default: `true`
|
||||
- Per-user and IP backstop limit values are configured in **Settings → Admin → Site features** and stored in DB runtime settings.
|
||||
- Optional first-boot seeding can be done via `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH` (`runtimeConfig.disableTtsRateLimit`).
|
||||
|
||||
## Related docs
|
||||
|
||||
- TTS/rate-limit environment variables: [Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||
- PDF parsing rate limiting (separate, compute-side throttle): [Admin Panel → Site features](./admin-panel#site-features) and [Environment Variables](../reference/environment-variables#compute-pdf-parsing-rate-limiting-runtime-settings)
|
||||
- Auth configuration: [Auth](./auth)
|
||||
- Provider setup: [TTS Providers](./tts-providers)
|
||||
132
docs-site/versioned_docs/version-v4.0.1/deploy/compute-worker.md
Normal file
132
docs-site/versioned_docs/version-v4.0.1/deploy/compute-worker.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
title: Compute Worker
|
||||
description: Deploy the standalone worker used for Whisper alignment and PDF layout parsing.
|
||||
---
|
||||
|
||||
Use this guide when OpenReader runs compute as a separate service. For the default embedded/local flow (`pnpm dev` or `pnpm start` without `COMPUTE_WORKER_URL`), configure the root `.env` instead and see [Local Development](./local-development).
|
||||
|
||||
## What the worker does
|
||||
|
||||
- Runs Whisper word alignment jobs
|
||||
- Runs PDF layout parsing jobs
|
||||
- Stores durable job state in NATS JetStream and NATS KV
|
||||
|
||||
The app server submits work to `POST /ops` and listens for updates on `GET /ops/:opId/events`.
|
||||
|
||||
## When to use it
|
||||
|
||||
- Required for Vercel-style deployments where heavy compute must run outside the app server
|
||||
- Useful when you want a dedicated compute host
|
||||
- Not needed for the default embedded local flow
|
||||
|
||||
## Container image
|
||||
|
||||
- `ghcr.io/richardr1126/openreader-compute-worker:latest`
|
||||
|
||||
## Worker environment
|
||||
|
||||
Required worker variables:
|
||||
|
||||
```env
|
||||
COMPUTE_WORKER_TOKEN=...
|
||||
NATS_URL=nats://...
|
||||
S3_BUCKET=...
|
||||
S3_REGION=...
|
||||
S3_ACCESS_KEY_ID=...
|
||||
S3_SECRET_ACCESS_KEY=...
|
||||
```
|
||||
|
||||
:::important
|
||||
`compute/worker/.env*` is only for standalone worker deployments.
|
||||
|
||||
- Embedded/local mode: configure the root `.env` only.
|
||||
- External worker mode: set `COMPUTE_WORKER_URL` and `COMPUTE_WORKER_TOKEN` on the app, and worker runtime values on the worker service.
|
||||
- Keep shared values aligned across app and worker: `COMPUTE_WORKER_TOKEN`, `S3_*`, `COMPUTE_WHISPER_TIMEOUT_MS`, `COMPUTE_PDF_TIMEOUT_MS`, and `COMPUTE_OP_STALE_MS`.
|
||||
:::
|
||||
|
||||
Common optional variables:
|
||||
|
||||
- `NATS_CREDS` or `NATS_CREDS_FILE`
|
||||
- `S3_ENDPOINT`, `S3_FORCE_PATH_STYLE=true`, `S3_PREFIX=openreader`
|
||||
- `COMPUTE_WORKER_HOST=0.0.0.0`
|
||||
- `PORT=8081` for local/manual runs. Platforms like Railway usually inject `PORT`.
|
||||
- `LOG_FORMAT=json` and `COMPUTE_LOG_LEVEL=info`
|
||||
- `COMPUTE_PREWARM_MODELS=true`
|
||||
- `COMPUTE_JOB_CONCURRENCY=1`
|
||||
- `COMPUTE_WHISPER_TIMEOUT_MS=30000`
|
||||
- `COMPUTE_PDF_TIMEOUT_MS=300000`
|
||||
- `COMPUTE_PDF_JOB_ATTEMPTS=1`
|
||||
- `COMPUTE_JOBS_STREAM_MAX_BYTES=268435456`
|
||||
- `COMPUTE_EVENTS_STREAM_MAX_BYTES=134217728`
|
||||
- `COMPUTE_JOB_STATES_MAX_BYTES=67108864`
|
||||
- `COMPUTE_NATS_REPLICAS=1`
|
||||
- `COMPUTE_OP_STALE_MS=1800000`
|
||||
- `WHISPER_MODEL_BASE_URL`
|
||||
- `PDF_LAYOUT_MODEL_BASE_URL`
|
||||
|
||||
If you need the broader app config reference, see [Environment Variables](../reference/environment-variables).
|
||||
|
||||
## App server environment
|
||||
|
||||
Set these on the Next.js app server:
|
||||
|
||||
```env
|
||||
COMPUTE_WORKER_URL=https://worker.example.com
|
||||
COMPUTE_WORKER_TOKEN=<same-token-as-worker>
|
||||
# Optional shared overrides:
|
||||
# COMPUTE_WHISPER_TIMEOUT_MS=30000
|
||||
# COMPUTE_PDF_TIMEOUT_MS=300000
|
||||
# COMPUTE_OP_STALE_MS=1800000
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Model artifact overrides (`WHISPER_MODEL_BASE_URL`, `PDF_LAYOUT_MODEL_BASE_URL`) belong on the worker service, not the app server.
|
||||
- There is no app-local compute fallback once `COMPUTE_WORKER_URL` is set. If the worker is unavailable, worker-backed requests fail.
|
||||
|
||||
## Deployment notes
|
||||
|
||||
- App and worker must share the same object storage.
|
||||
- Embedded `weed mini` is not supported for external worker mode.
|
||||
- Protect `COMPUTE_WORKER_TOKEN` and do not expose worker routes without auth.
|
||||
- The worker connects to NATS lazily and disconnects after 120 seconds of full idle time. That allows platforms like Railway to sleep the service, but the first request after a cold start will be slower.
|
||||
|
||||
## Health endpoints
|
||||
|
||||
- `GET /health/live` returns `{ ok: true }`.
|
||||
- `GET /health/ready` returns `{ ok: true, natsConnected }` and reflects the current NATS session without forcing a reconnect.
|
||||
|
||||
## Railway + Synadia example
|
||||
|
||||
Deploy the worker image to Railway and set worker env vars similar to:
|
||||
|
||||
```env
|
||||
COMPUTE_WORKER_HOST=0.0.0.0
|
||||
COMPUTE_WORKER_TOKEN=<shared-token>
|
||||
NATS_URL=tls://connect.ngs.global:4222
|
||||
NATS_CREDS="-----BEGIN NATS USER JWT-----
|
||||
...
|
||||
------END USER NKEY SEED------"
|
||||
S3_BUCKET=<bucket>
|
||||
S3_REGION=<region>
|
||||
S3_ACCESS_KEY_ID=<key>
|
||||
S3_SECRET_ACCESS_KEY=<secret>
|
||||
# Optional:
|
||||
# S3_ENDPOINT=https://...
|
||||
# S3_FORCE_PATH_STYLE=true
|
||||
# S3_PREFIX=openreader
|
||||
```
|
||||
|
||||
If your platform supports mounted files, you can use `NATS_CREDS_FILE` instead of `NATS_CREDS`.
|
||||
|
||||
Set these on the OpenReader app server:
|
||||
|
||||
```env
|
||||
COMPUTE_WORKER_URL=https://<railway-worker-domain>
|
||||
COMPUTE_WORKER_TOKEN=<same-token-as-worker>
|
||||
```
|
||||
|
||||
Verify the worker after deploy:
|
||||
|
||||
- `GET https://<railway-worker-domain>/health/live`
|
||||
- `GET https://<railway-worker-domain>/health/ready`
|
||||
|
|
@ -0,0 +1,371 @@
|
|||
---
|
||||
title: Local Development
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<details>
|
||||
<summary><strong>Node.js + pnpm (required)</strong></summary>
|
||||
|
||||
<Tabs groupId="local-dev-node-pnpm-os">
|
||||
<TabItem value="macos" label="macOS" default>
|
||||
|
||||
```bash
|
||||
brew install nvm pnpm
|
||||
mkdir -p ~/.nvm
|
||||
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc
|
||||
echo '[ -s "$(brew --prefix nvm)/nvm.sh" ] && . "$(brew --prefix nvm)/nvm.sh"' >> ~/.zshrc
|
||||
source ~/.zshrc
|
||||
nvm install --lts
|
||||
nvm use --lts
|
||||
node -v
|
||||
pnpm -v
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu example
|
||||
sudo apt update
|
||||
sudo apt install -y curl
|
||||
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||
nvm install --lts
|
||||
nvm use --lts
|
||||
corepack enable
|
||||
corepack prepare pnpm@latest --activate
|
||||
node -v
|
||||
pnpm -v
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>SeaweedFS <code>weed</code> binary (required unless using external S3)</strong></summary>
|
||||
|
||||
<Tabs groupId="local-dev-seaweed-os">
|
||||
<TabItem value="macos" label="macOS" default>
|
||||
|
||||
```bash
|
||||
brew install seaweedfs
|
||||
weed version
|
||||
```
|
||||
|
||||
:::warning SeaweedFS Compatibility Note (April 16, 2026)
|
||||
If you see intermittent S3 `InternalError` upload failures with embedded storage, use SeaweedFS `4.18`.
|
||||
OpenReader currently pins `4.18` in CI and Docker builds while `4.19` compatibility is investigated.
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
|
||||
```bash
|
||||
# Linux amd64 example (pin 4.18)
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
curl -fsSL -o /tmp/seaweedfs.tar.gz \
|
||||
https://github.com/seaweedfs/seaweedfs/releases/download/4.18/linux_amd64.tar.gz
|
||||
tar -xzf /tmp/seaweedfs.tar.gz -C /tmp weed
|
||||
install -m 0755 /tmp/weed "$HOME/.local/bin/weed"
|
||||
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
weed version
|
||||
```
|
||||
|
||||
:::warning SeaweedFS Compatibility Note (April 16, 2026)
|
||||
If you see intermittent S3 `InternalError` upload failures with embedded storage, use SeaweedFS `4.18`.
|
||||
OpenReader currently pins `4.18` in CI and Docker builds while `4.19` compatibility is investigated.
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>NATS Server <code>nats-server</code> (required for embedded compute mode)</strong></summary>
|
||||
|
||||
If `COMPUTE_WORKER_URL` is unset, startup launches embedded compute worker + NATS, so `nats-server` must be available on host PATH.
|
||||
|
||||
If you always use an external worker (`COMPUTE_WORKER_URL` set), this is not required.
|
||||
|
||||
<Tabs groupId="local-dev-nats-os">
|
||||
<TabItem value="macos" label="macOS" default>
|
||||
|
||||
```bash
|
||||
brew install nats-server
|
||||
nats-server -v
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
|
||||
```bash
|
||||
# Linux amd64 example
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
curl -fsSL -o /tmp/nats-server.zip \
|
||||
https://github.com/nats-io/nats-server/releases/latest/download/nats-server-v2.12.1-linux-amd64.zip
|
||||
unzip -j /tmp/nats-server.zip '*/nats-server' -d /tmp
|
||||
install -m 0755 /tmp/nats-server "$HOME/.local/bin/nats-server"
|
||||
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
nats-server -v
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>LibreOffice (optional, for DOCX conversion)</strong></summary>
|
||||
|
||||
<Tabs groupId="local-dev-libreoffice-os">
|
||||
<TabItem value="macos" label="macOS" default>
|
||||
|
||||
```bash
|
||||
brew install libreoffice
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="linux" label="Linux">
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu example
|
||||
sudo apt update
|
||||
sudo apt install -y libreoffice
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Word-by-word highlighting (optional)</strong></summary>
|
||||
|
||||
No extra native Whisper CLI build step is required.
|
||||
|
||||
Word-by-word highlighting and PDF layout parsing are worker-backed in current releases.
|
||||
|
||||
If you need mirrors or pinned artifact locations, set `WHISPER_MODEL_BASE_URL` in `.env` (current defaults expect q4 Whisper files at that base URL).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>External compute worker dev stack (optional)</strong></summary>
|
||||
|
||||
Use this only when you intentionally run compute-worker as a separate service.
|
||||
Default local flow does not need `compute/worker/.env`; embedded worker startup reads root `.env`.
|
||||
Full worker deployment details are in [Compute Worker (NATS JetStream)](./compute-worker).
|
||||
|
||||
Start only NATS + compute-worker via compose watch:
|
||||
|
||||
```bash
|
||||
docker compose --env-file compute/worker/.env -f compute/worker/docker-compose.yml up --watch
|
||||
# or: pnpm compute:dev:watch
|
||||
```
|
||||
|
||||
`compute/worker/.env.example` contains a starter config for standalone worker service deployments.
|
||||
|
||||
Run the main app separately on the host:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
For app -> external worker routing, set in root `.env`:
|
||||
|
||||
```env
|
||||
COMPUTE_WORKER_URL=http://localhost:8081
|
||||
COMPUTE_WORKER_TOKEN=<same-token-used-by-worker>
|
||||
```
|
||||
|
||||
Ownership in external worker mode:
|
||||
- root `.env`: app routing/auth (`COMPUTE_WORKER_URL`, `COMPUTE_WORKER_TOKEN`) plus optional shared timeout/stale overrides
|
||||
- `compute/worker/.env*` (or worker platform env): worker runtime variables (`NATS_*`, `S3_*`, model base URLs, worker tuning)
|
||||
|
||||
Worker mode requires worker-reachable shared object storage (S3-compatible endpoint).
|
||||
For external worker mode, object storage must be shared/reachable by both app and worker services.
|
||||
|
||||
</details>
|
||||
|
||||
## Steps
|
||||
|
||||
### Required flow
|
||||
|
||||
1. Clone the repository.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/richardr1126/openreader.git
|
||||
cd openreader
|
||||
```
|
||||
|
||||
2. Install dependencies.
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
|
||||
3. Configure the environment.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Then edit `.env`.
|
||||
|
||||
Default embedded worker flow (no external worker URL):
|
||||
|
||||
```env
|
||||
# Leave COMPUTE_WORKER_URL unset.
|
||||
# Entry point auto-starts embedded worker+NATS when available.
|
||||
```
|
||||
|
||||
External worker flow:
|
||||
|
||||
```env
|
||||
COMPUTE_WORKER_URL=http://localhost:8081
|
||||
COMPUTE_WORKER_TOKEN=<same-token-used-by-worker>
|
||||
```
|
||||
|
||||
Use the same ownership split:
|
||||
- root `.env`: app routing/auth (`COMPUTE_WORKER_URL`, `COMPUTE_WORKER_TOKEN`) plus optional shared timeout/stale overrides
|
||||
- `compute/worker/.env*` (or worker platform env): worker runtime variables (`NATS_*`, `S3_*`, model base URLs, worker tuning)
|
||||
|
||||
Use one of these `.env` mode templates:
|
||||
|
||||
<Tabs groupId="local-env-modes">
|
||||
<TabItem value="auth-enabled" label="Auth Enabled" default>
|
||||
|
||||
```env
|
||||
API_BASE=http://host.docker.internal:8880/v1
|
||||
API_KEY=none
|
||||
BASE_URL=http://localhost:3003
|
||||
AUTH_SECRET=<generate-with-openssl-rand-hex-32>
|
||||
# Optional when you need multiple local origins:
|
||||
# AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="auth-with-admin" label="Auth + Admin Panel">
|
||||
|
||||
```env
|
||||
# API_BASE / API_KEY are seeded into the admin "default-openai" shared provider
|
||||
# on first boot, then no longer read. Manage them in Settings → Admin afterwards.
|
||||
API_BASE=http://host.docker.internal:8880/v1
|
||||
API_KEY=none
|
||||
BASE_URL=http://localhost:3003
|
||||
AUTH_SECRET=<generate-with-openssl-rand-hex-32>
|
||||
# Comma-separated emails to auto-promote to admin on signin.
|
||||
ADMIN_EMAILS=you@example.com
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="external-s3" label="External S3">
|
||||
|
||||
```env
|
||||
API_BASE=http://host.docker.internal:8880/v1
|
||||
API_KEY=none
|
||||
USE_EMBEDDED_WEED_MINI=false
|
||||
BASE_URL=http://localhost:3003
|
||||
AUTH_SECRET=<generate-with-openssl-rand-hex-32>
|
||||
S3_BUCKET=your-bucket
|
||||
S3_REGION=us-east-1
|
||||
S3_ACCESS_KEY_ID=your-access-key
|
||||
S3_SECRET_ACCESS_KEY=your-secret-key
|
||||
# Optional for non-AWS providers:
|
||||
# S3_ENDPOINT=https://your-s3-compatible-endpoint
|
||||
# S3_FORCE_PATH_STYLE=true
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="worker-mode" label="External Worker Service">
|
||||
|
||||
```env
|
||||
API_BASE=http://host.docker.internal:8880/v1
|
||||
API_KEY=none
|
||||
BASE_URL=http://localhost:3003
|
||||
AUTH_SECRET=<generate-with-openssl-rand-hex-32>
|
||||
COMPUTE_WORKER_URL=http://localhost:8081
|
||||
COMPUTE_WORKER_TOKEN=<same-token-used-by-worker>
|
||||
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
|
||||
# Optional for non-AWS providers:
|
||||
# S3_ENDPOINT=https://your-s3-compatible-endpoint
|
||||
# S3_FORCE_PATH_STYLE=true
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note Env vars vs. admin panel
|
||||
On first boot, `API_KEY` / `API_BASE` can bootstrap `default-openai`, and `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH` can seed runtime config + providers. After that, the admin UI is authoritative and editing bootstrap env vars no longer changes app behavior. See [Admin Panel](../configure/admin-panel).
|
||||
:::
|
||||
|
||||
:::note User BYOK restriction default
|
||||
If you want each user to enter personal provider credentials, set `restrictUserApiKeys=false` (from **Settings → Admin**, or by seeding `runtimeConfig.restrictUserApiKeys=false` in runtime seed JSON).
|
||||
:::
|
||||
|
||||
:::info
|
||||
For all environment variables, see [Environment Variables](../reference/environment-variables).
|
||||
:::
|
||||
|
||||
See [Auth](../configure/auth) for app/auth behavior.
|
||||
See [Admin Panel](../configure/admin-panel) for the shared-provider and feature-flag management UI.
|
||||
Storage configuration details are in [Object / Blob Storage](../configure/object-blob-storage).
|
||||
Refer to [Database](../configure/database) for database modes.
|
||||
Learn about migration behavior and commands in [Migrations](../configure/migrations).
|
||||
|
||||
4. Start the app.
|
||||
|
||||
<Tabs groupId="local-run-mode">
|
||||
<TabItem value="dev" label="Dev (recommended)" default>
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
If you use embedded worker startup (no `COMPUTE_WORKER_URL`) and the host is missing `nats-server`,
|
||||
install `nats-server` locally or switch to external worker mode.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="prod" label="Build + Start">
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm start
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::warning API Base Reachability
|
||||
`API_BASE` must be reachable from the Next.js server process, not just your browser.
|
||||
:::
|
||||
|
||||
Visit [http://localhost:3003](http://localhost:3003).
|
||||
|
||||
### Optional workflows
|
||||
|
||||
Run manual DB migrations only for troubleshooting or explicit migration workflows:
|
||||
|
||||
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
|
||||
|
||||
```bash
|
||||
pnpm migrate
|
||||
```
|
||||
|
||||
:::info
|
||||
If `POSTGRES_URL` is set, migrations target Postgres; otherwise local SQLite is used. To disable automatic startup migrations, set `RUN_DRIZZLE_MIGRATIONS=false` and/or `RUN_FS_MIGRATIONS=false`. You can run storage migration manually with `pnpm migrate-fs`.
|
||||
:::
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
---
|
||||
title: Vercel Deployment
|
||||
---
|
||||
|
||||
This guide covers deploying OpenReader to Vercel with external Postgres and S3-compatible object storage.
|
||||
|
||||
## What works on Vercel
|
||||
|
||||
- Documents (PDF/EPUB/TXT/MD) work with `POSTGRES_URL` + external S3 storage.
|
||||
- Audiobook routes work on Node.js serverless functions using `ffmpeg-static`.
|
||||
- Heavy compute features (Whisper alignment + PDF layout parsing) run through an external compute worker service.
|
||||
- For worker setup details and worker-specific env vars, see [Compute Worker (NATS JetStream)](./compute-worker).
|
||||
|
||||
:::warning DOCX Conversion Limitation
|
||||
`docx` conversion requires `soffice` (LibreOffice), which is not available in a standard Vercel runtime.
|
||||
:::
|
||||
|
||||
## 1. Environment Variables
|
||||
|
||||
Recommended production setup (auth enabled, admin panel enabled):
|
||||
|
||||
```bash
|
||||
# Infrastructure
|
||||
POSTGRES_URL=postgres://...
|
||||
USE_EMBEDDED_WEED_MINI=false
|
||||
S3_ACCESS_KEY_ID=...
|
||||
S3_SECRET_ACCESS_KEY=...
|
||||
S3_BUCKET=...
|
||||
S3_REGION=us-east-1
|
||||
S3_PREFIX=openreader
|
||||
# Optional (non-AWS S3-compatible providers):
|
||||
# S3_ENDPOINT=https://...
|
||||
# S3_FORCE_PATH_STYLE=true
|
||||
|
||||
# Auth (required for the admin panel)
|
||||
BASE_URL=https://your-app.vercel.app
|
||||
AUTH_SECRET=...
|
||||
ADMIN_EMAILS=you@example.com # comma-separated; admins manage TTS + features in-app
|
||||
|
||||
# Heavy compute (required on Vercel in current releases)
|
||||
COMPUTE_WORKER_URL=https://<railway-worker-domain>
|
||||
COMPUTE_WORKER_TOKEN=...
|
||||
|
||||
# Logging (recommended for Vercel log ingestion)
|
||||
LOG_FORMAT=json
|
||||
LOG_LEVEL=info
|
||||
|
||||
# First-boot seed for the TTS shared provider (optional; manage in-app afterwards)
|
||||
# API_KEY=your_replicate_key
|
||||
# API_BASE only needed for OpenAI-compatible self-hosted providers
|
||||
```
|
||||
|
||||
If you also run an external worker service (for example Railway), set these there too:
|
||||
|
||||
- `LOG_FORMAT=json`
|
||||
- `COMPUTE_LOG_LEVEL=info`
|
||||
|
||||
:::note Env vars vs. admin panel (important for Vercel)
|
||||
`API_KEY` / `API_BASE` are one-shot bootstrap seeds on first deploy. After boot, manage providers and site features in **Settings → Admin**. Changes there apply on refresh without a redeploy. See [Admin Panel](../configure/admin-panel).
|
||||
:::
|
||||
|
||||
## 1a. Railway + Synadia quick start (worker mode)
|
||||
|
||||
If your Vercel app uses an external compute worker on Railway with Synadia Cloud (NGS):
|
||||
|
||||
1. Deploy a Railway service from:
|
||||
- `ghcr.io/richardr1126/openreader-compute-worker:refactor-ppdoclayoutv3-onnx-layout-parsing`
|
||||
2. Enable public networking on that Railway service and set:
|
||||
- `COMPUTE_WORKER_URL=https://<railway-worker-domain>` (in Vercel)
|
||||
3. Use the same `COMPUTE_WORKER_TOKEN` value in both Vercel and Railway worker env vars.
|
||||
|
||||
For complete Railway worker env vars (`NATS_*`, `S3_*`, health checks, and Synadia `.creds` guidance), see [Compute Worker (NATS JetStream)](./compute-worker).
|
||||
|
||||
## 2. First-run admin configuration (recommended)
|
||||
|
||||
After the first successful deploy and admin login, open **Settings → Admin** and configure:
|
||||
|
||||
- **Shared providers**: create/edit your provider key(s) here (encrypted at rest).
|
||||
- **Site features**:
|
||||
- `enableDocxConversion=false` on Vercel (`soffice` unavailable).
|
||||
- `enableDestructiveDeleteActions=false` for safer public deployments.
|
||||
- `enableTtsProvidersTab=false` if you want shared-provider-only UX.
|
||||
- `enableUserSignups=true` unless you explicitly want an invite-only deployment.
|
||||
- `restrictUserApiKeys=true` to block user BYOK through the hosted server.
|
||||
- `defaultTtsProvider=replicate` (or your preferred shared slug).
|
||||
- `showAllProviderModels=false` if you want users locked to each provider's default model.
|
||||
- `enableAudiobookExport=true`.
|
||||
|
||||
## 3. Runtime JSON seed (optional)
|
||||
|
||||
If you must pre-seed site features/providers at deploy time, use `RUNTIME_SEED_JSON` or `RUNTIME_SEED_JSON_PATH` (versioned JSON seed document). Prefer the admin panel for ongoing management.
|
||||
|
||||
See [Environment Variables](../reference/environment-variables#runtime-json-seed-v4) for schema and examples.
|
||||
|
||||
:::warning Auth recommendation
|
||||
Set both `BASE_URL` and `AUTH_SECRET` — they are required in v4+ and also required for the admin panel and for encrypting admin-stored TTS credentials.
|
||||
:::
|
||||
|
||||
:::warning Rotating AUTH_SECRET invalidates admin-stored keys
|
||||
Admin-managed TTS provider keys are encrypted with a key derived from `AUTH_SECRET`. If you rotate `AUTH_SECRET` after the first deploy, you must re-enter each admin shared provider's API key from the UI.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
|
||||
:::
|
||||
|
||||
## 4. Database and data migrations
|
||||
|
||||
Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so automatic startup migrations do not run there.
|
||||
|
||||
- Run `pnpm migrate` in a controlled environment to apply Drizzle schema migrations to your Postgres DB.
|
||||
- Run `pnpm migrate-fs` only when migrating legacy local filesystem data (`docstore/documents_v1`, `docstore/audiobooks_v1`) into object storage + DB rows. Fresh Vercel deployments usually do not need this.
|
||||
|
||||
## 5. FFmpeg packaging in Vercel functions
|
||||
|
||||
`ffmpeg-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for:
|
||||
|
||||
- `/api/audiobook`
|
||||
- `/api/audiobook/chapter`
|
||||
- `/api/tts/segments/ensure`
|
||||
|
||||
:::info
|
||||
`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
|
||||
:::
|
||||
|
||||
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
|
||||
|
||||
## 6. Function memory sizing
|
||||
|
||||
FFmpeg workloads benefit from more memory/CPU. This repo includes:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"functions": {
|
||||
"app/api/audiobook/route.ts": { "memory": 3009 },
|
||||
"app/api/tts/segments/ensure/route.ts": { "memory": 3009 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adjust memory per route if your files are larger or your plan differs.
|
||||
|
||||
## 7. Runtime expectations and caveats
|
||||
|
||||
- Audiobook APIs require S3 configuration; otherwise they return `503`.
|
||||
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
|
||||
|
||||
## 8. Smoke test after deploy
|
||||
|
||||
1. Upload and read a PDF/EPUB document.
|
||||
2. Confirm sync/blob fetch works across refreshes/devices.
|
||||
3. Generate at least one audiobook chapter and play/download it.
|
||||
4. Verify worker-backed word highlighting and PDF parsing.
|
||||
164
docs-site/versioned_docs/version-v4.0.1/docker-quick-start.md
Normal file
164
docs-site/versioned_docs/version-v4.0.1/docker-quick-start.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
---
|
||||
title: Docker Quick Start
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A recent Docker version installed
|
||||
- A TTS API server that OpenReader can reach:
|
||||
- [Kokoro-FastAPI](./configure/tts-provider-guides/kokoro-fastapi)
|
||||
- [KittenTTS-FastAPI](./configure/tts-provider-guides/kitten-tts-fastapi)
|
||||
- [Orpheus-FastAPI](./configure/tts-provider-guides/orpheus-fastapi)
|
||||
- [Replicate](./configure/tts-provider-guides/replicate)
|
||||
- [DeepInfra](./configure/tts-provider-guides/deepinfra)
|
||||
- [OpenAI](./configure/tts-provider-guides/openai)
|
||||
- [Other OpenAI-compatible providers](./configure/tts-provider-guides/other)
|
||||
|
||||
:::warning SeaweedFS Compatibility Note (April 16, 2026)
|
||||
OpenReader currently pins embedded SeaweedFS to `4.18` in CI and Docker builds.
|
||||
`4.19` introduced intermittent `InternalError` responses on S3 `PutObject` in our upload flow.
|
||||
:::
|
||||
|
||||
## Published images
|
||||
|
||||
- App server: `ghcr.io/richardr1126/openreader:latest`
|
||||
- Compute worker (Optional): `ghcr.io/richardr1126/openreader-compute-worker:latest`
|
||||
- Legacy app alias: `ghcr.io/richardr1126/openreader-webui:latest`
|
||||
|
||||
## 1. Start the Docker container
|
||||
|
||||
<Tabs groupId="docker-start-mode">
|
||||
<TabItem value="localhost" label="Localhost" default>
|
||||
|
||||
Persistent storage, embedded SeaweedFS `weed mini`, required auth, optional library mount:
|
||||
|
||||
```bash
|
||||
docker run --name openreader \
|
||||
--restart unless-stopped \
|
||||
-p 3003:3003 \
|
||||
-p 8333:8333 \
|
||||
-v openreader_docstore:/app/docstore \
|
||||
-e API_BASE=http://host.docker.internal:8880/v1 \
|
||||
-e API_KEY=none \
|
||||
-e BASE_URL=http://localhost:3003 \
|
||||
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||
-e ADMIN_EMAILS=you@example.com \
|
||||
ghcr.io/richardr1126/openreader:latest
|
||||
```
|
||||
|
||||
What this command enables:
|
||||
|
||||
- `-p 3003:3003`: exposes the OpenReader web app/API.
|
||||
- `-p 8333:8333`: exposes embedded SeaweedFS S3 endpoint for direct browser presigned upload/download.
|
||||
- `-v openreader_docstore:/app/docstore`: persists SQLite metadata, SeaweedFS blob data, and migration/runtime state.
|
||||
- `-e API_BASE=...` / `-e API_KEY=...`: **first-boot seed only.** On the first container start, these are auto-migrated into a `default-openai` admin shared provider stored in the DB (key encrypted at rest). After that, the running app no longer reads them — manage the provider from **Settings → Admin → Shared providers**. See [Admin Panel](./configure/admin-panel).
|
||||
- `-e BASE_URL=...` and `-e AUTH_SECRET=...`: required for v4+ auth/session startup.
|
||||
- `-e ADMIN_EMAILS=...`: (optional, requires auth) comma-separated emails auto-promoted to admin. Admins see the **Admin** tab in Settings.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="local-network" label="LAN Host">
|
||||
|
||||
Use this when the app should be reachable from other devices on your LAN:
|
||||
|
||||
```bash
|
||||
docker run --name openreader \
|
||||
--restart unless-stopped \
|
||||
-p 3003:3003 \
|
||||
-p 8333:8333 \
|
||||
-v openreader_docstore:/app/docstore \
|
||||
-e API_BASE=http://host.docker.internal:8880/v1 \
|
||||
-e BASE_URL=http://<YOUR_LAN_IP>:3003 \
|
||||
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||
-e AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 \
|
||||
-e USE_ANONYMOUS_AUTH_SESSIONS=true \
|
||||
-e ADMIN_EMAILS=you@example.com \
|
||||
ghcr.io/richardr1126/openreader:latest
|
||||
```
|
||||
|
||||
Replace `YOUR_LAN_IP` with the Docker host IP address on your local network to allow access from other devices.
|
||||
|
||||
What this command enables:
|
||||
|
||||
- LAN access from phones/tablets/other computers via `http://<YOUR_LAN_IP>:3003`.
|
||||
- `BASE_URL` points auth/session cookies and callbacks at your LAN URL.
|
||||
- `AUTH_TRUSTED_ORIGINS` allows localhost loopback origins in addition to your primary LAN origin.
|
||||
- `USE_ANONYMOUS_AUTH_SESSIONS=true` allows guest sessions while auth is enabled.
|
||||
- `API_BASE` seeds the default TTS endpoint into the admin-managed `default-openai` shared provider on first boot. Edit it from **Settings → Admin → Shared providers** after that.
|
||||
- `ADMIN_EMAILS=...` (optional) auto-promotes the listed email(s) to admin so they can manage shared providers and site feature flags from the UI.
|
||||
- `openreader_docstore` volume keeps data persistent across restarts.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="minimal" label="Minimal">
|
||||
|
||||
Auth required, embedded storage ephemeral, no library import:
|
||||
|
||||
```bash
|
||||
docker run --name openreader \
|
||||
--restart unless-stopped \
|
||||
-p 3003:3003 \
|
||||
-p 8333:8333 \
|
||||
-e BASE_URL=http://localhost:3003 \
|
||||
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||
ghcr.io/richardr1126/openreader:latest
|
||||
```
|
||||
|
||||
What this command enables:
|
||||
|
||||
- Fast startup with only the required auth env vars.
|
||||
- No persistent volume (`/app/docstore` stays container-local), so data is ephemeral unless you add a mount.
|
||||
- The app still requires `BASE_URL` + `AUTH_SECRET` in v4+, so include them even in minimal mode.
|
||||
- No TTS provider preset by default. Configure `API_BASE`/`API_KEY` on first boot if you want a seeded shared provider, or run auth+admin mode and manage providers from the admin panel.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip Quick Tips
|
||||
- Set `API_BASE` on first boot to a TTS endpoint the container can reach (`host.docker.internal` works for host-local services). After first boot, manage providers in **Settings → Admin → Shared providers**.
|
||||
- `BASE_URL` and `AUTH_SECRET` are required in v4+. The admin panel requires auth.
|
||||
- Set `ADMIN_EMAILS` to your email if you want the **Admin** tab in Settings.
|
||||
- `restrictUserApiKeys` controls shared-provider-only mode. For per-user BYOK, toggle it off in **Settings → Admin → Site features** or seed `runtimeConfig.restrictUserApiKeys=false` via runtime seed JSON.
|
||||
- Use a `/app/docstore` mount if you want data to survive container/image replacement.
|
||||
- Startup automatically runs DB/storage migrations via the shared entrypoint.
|
||||
:::
|
||||
|
||||
:::warning Port `8333` Exposure
|
||||
Expose `8333` for direct browser presigned upload/download with embedded SeaweedFS.
|
||||
|
||||
If `8333` is not reachable from the browser, direct presigned access is unavailable. Uploads can still fall back to `/api/documents/blob/upload/fallback`, and document reads/downloads continue through `/api/documents/blob`.
|
||||
:::
|
||||
|
||||
## 2. Configure settings in the app UI
|
||||
|
||||
Visit [http://localhost:3003](http://localhost:3003) after startup.
|
||||
|
||||
- If you set `ADMIN_EMAILS`, sign in with that email and open **Settings → Admin** to manage shared TTS providers and site feature flags for all users.
|
||||
- Per-user: set TTS provider/model in **Settings → TTS Provider**. API key/base URL inputs are shown only when `restrictUserApiKeys=false`.
|
||||
- Select the model voice from the voice dropdown.
|
||||
|
||||
## 3. Update Docker image
|
||||
|
||||
Legacy image compatibility: `ghcr.io/richardr1126/openreader-webui:latest` remains available as an alias.
|
||||
For external compute mode image details, see [Compute Worker (NATS JetStream)](./deploy/compute-worker).
|
||||
|
||||
```bash
|
||||
docker stop openreader || true && \
|
||||
docker rm openreader || true && \
|
||||
docker image rm ghcr.io/richardr1126/openreader:latest || true && \
|
||||
docker pull ghcr.io/richardr1126/openreader:latest
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you use a mounted volume for `/app/docstore`, your persisted data remains after image updates.
|
||||
:::
|
||||
|
||||
:::info Related Docs
|
||||
- [Environment Variables](./reference/environment-variables)
|
||||
- [Auth](./configure/auth)
|
||||
- [Admin Panel](./configure/admin-panel)
|
||||
- [Database](./configure/database)
|
||||
- [Object / Blob Storage](./configure/object-blob-storage)
|
||||
- [Migrations](./configure/migrations)
|
||||
:::
|
||||
44
docs-site/versioned_docs/version-v4.0.1/introduction.md
Normal file
44
docs-site/versioned_docs/version-v4.0.1/introduction.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
id: intro
|
||||
title: Introduction
|
||||
slug: /
|
||||
---
|
||||
|
||||
OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
|
||||
|
||||
> Previously named **OpenReader-WebUI**.
|
||||
|
||||
It supports multiple TTS providers including OpenAI, Replicate, DeepInfra, and custom OpenAI-compatible endpoints such as [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI), [KittenTTS-FastAPI](https://github.com/richardr1126/KittenTTS-FastAPI), and [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI).
|
||||
|
||||
## ✨ Highlights
|
||||
|
||||
- 🧱 **Layout-aware PDF Parsing**
|
||||
- PP-DocLayoutV3 (ONNX) detects structured blocks with cross-page stitching and geometry-based highlighting for precise read-along sync and clean TTS segmentation
|
||||
- ⏱️ **Word-by-word Highlighting** via ONNX Whisper alignment
|
||||
- Powered by the external compute worker control plane (NATS JetStream-backed)
|
||||
- ⚡ **Segment-based TTS Playback**
|
||||
- Sentence-aware generation with cached audio segments, background preloading, and resumable playback across EPUB, PDF, TXT, MD, and DOCX
|
||||
- 🎯 **Multi-Provider TTS Support**
|
||||
- Self-hosted: [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI) (multi-voice combinations), [**KittenTTS-FastAPI**](https://github.com/richardr1126/KittenTTS-FastAPI), [**Orpheus-FastAPI**](https://github.com/Lex-au/Orpheus-FastAPI), or any custom OpenAI-compatible endpoint
|
||||
- Cloud: [**OpenAI**](https://platform.openai.com/docs/pricing#transcription-and-speech) (`tts-1`, `tts-1-hd`, `gpt-4o-mini-tts`), [**Replicate**](https://replicate.com/explore) (built-in catalog + any model ID), [**DeepInfra**](https://deepinfra.com/models/text-to-speech) (Kokoro-82M and others)
|
||||
- 🎧 **Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
|
||||
- 🗂️ **Flexible Backend** — embedded SeaweedFS or S3-compatible storage, SQLite or Postgres, server library import, and device sync
|
||||
- 🔐 **Auth and User Isolation** — auth is required in v4+, with optional anonymous auth sessions for guest flows
|
||||
- 🎨 **Customizable** — 13 built-in themes (light and dark palettes), per-user TTS settings, and document handling controls
|
||||
|
||||
## 🧭 Key Docs
|
||||
|
||||
- [Docker Quick Start](./docker-quick-start)
|
||||
- [Local Development](./deploy/local-development)
|
||||
- [Vercel Deployment](./deploy/vercel-deployment)
|
||||
- [Environment Variables](./reference/environment-variables)
|
||||
- [Auth](./configure/auth)
|
||||
- [Database](./configure/database)
|
||||
- [Object / Blob Storage](./configure/object-blob-storage)
|
||||
- [Migrations](./configure/migrations)
|
||||
- [Server Library Import](./configure/server-library-import)
|
||||
- [TTS Providers](./configure/tts-providers)
|
||||
|
||||
## Source Repository
|
||||
|
||||
- GitHub: [richardr1126/openreader](https://github.com/richardr1126/openreader)
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
---
|
||||
title: Environment Variables
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
This page is the source-of-truth reference for OpenReader environment variables.
|
||||
|
||||
:::note Recommended configuration path
|
||||
Use **Settings → Admin** as the primary source of truth for shared providers and runtime site features.
|
||||
`API_BASE` / `API_KEY` are optional one-time provider bootstrap seeds.
|
||||
Runtime site features are seeded with `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH`.
|
||||
:::
|
||||
|
||||
## Quick Reference Table
|
||||
|
||||
| Variable | Area | Default | When to set |
|
||||
| --- | --- | --- | --- |
|
||||
| `LOG_FORMAT` | Runtime logging | `pretty` | Set `json` for structured logs |
|
||||
| `LOG_LEVEL` | Runtime logging | `info` | Set app server log level |
|
||||
| `API_BASE` | TTS provider bootstrap seed | unset | Optional first-boot base URL for `default-openai` |
|
||||
| `API_KEY` | TTS provider bootstrap seed | unset | Optional first-boot API key for `default-openai` |
|
||||
| `BASE_URL` | Auth | unset | Required at startup |
|
||||
| `AUTH_SECRET` | Auth | unset | Required at startup |
|
||||
| `AUTH_TRUSTED_ORIGINS` | Auth | empty | Add extra allowed origins |
|
||||
| `USE_ANONYMOUS_AUTH_SESSIONS` | Auth | `false` | Set `true` to allow anonymous auth sessions |
|
||||
| `GITHUB_CLIENT_ID` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_SECRET` to enable GitHub sign-in |
|
||||
| `GITHUB_CLIENT_SECRET` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_ID` to enable GitHub sign-in |
|
||||
| `ADMIN_EMAILS` | Admin | empty | Comma-separated emails auto-promoted to admin |
|
||||
| `POSTGRES_URL` | Database | unset (SQLite mode) | Set to switch metadata/auth DB to Postgres |
|
||||
| `USE_EMBEDDED_WEED_MINI` | Storage | `true` when unset | Set `false` to use external S3-compatible storage only |
|
||||
| `WEED_MINI_DIR` | Storage | `docstore/seaweedfs` | Override embedded SeaweedFS data directory |
|
||||
| `WEED_MINI_WAIT_SEC` | Storage | `20` | Tune SeaweedFS startup wait timeout |
|
||||
| `S3_ACCESS_KEY_ID` | Storage | auto-generated in embedded mode | Set explicitly for stable/external credentials |
|
||||
| `S3_SECRET_ACCESS_KEY` | Storage | auto-generated in embedded mode | Set explicitly for stable/external credentials |
|
||||
| `S3_BUCKET` | Storage | `openreader-documents` in embedded mode | Required for external S3-compatible storage |
|
||||
| `S3_REGION` | Storage | `us-east-1` in embedded mode | Required for external S3-compatible storage |
|
||||
| `S3_ENDPOINT` | Storage | derived in embedded mode | Set for S3-compatible providers (MinIO/SeaweedFS/R2/etc.) |
|
||||
| `S3_FORCE_PATH_STYLE` | Storage | `true` in embedded mode | Set per provider requirement |
|
||||
| `S3_PREFIX` | Storage | `openreader` | Customize object key prefix |
|
||||
| `IMPORT_LIBRARY_DIR` | Library import | `docstore/library` fallback | Set a single server library root |
|
||||
| `IMPORT_LIBRARY_DIRS` | Library import | unset | Set multiple roots (comma/colon/semicolon separated) |
|
||||
| `EMBEDDED_COMPUTE_WORKER_PORT` | Compute | `8081` | Override embedded worker bind port |
|
||||
| `EMBEDDED_NATS_PORT` | Compute | `4222` | Override embedded NATS client port |
|
||||
| `EMBEDDED_NATS_MONITOR_PORT` | Compute | `8222` | Override embedded NATS monitor port |
|
||||
| `EMBEDDED_NATS_STORE_DIR` | Compute | `docstore/nats/jetstream` | Override embedded JetStream storage directory |
|
||||
| `NATS_URL` | Compute | `nats://127.0.0.1:4222` in embedded startup | Override embedded startup or set standalone worker URL |
|
||||
| `COMPUTE_LOG_LEVEL` | Compute | `info` | Compute worker log level |
|
||||
| `COMPUTE_JOB_CONCURRENCY` | Compute | `1` | Shared compute concurrency cap |
|
||||
| `COMPUTE_WHISPER_TIMEOUT_MS` | Compute | `30000` | Whisper alignment timeout budget |
|
||||
| `COMPUTE_PDF_TIMEOUT_MS` | Compute | `300000` | PDF parse timeout budget |
|
||||
| `COMPUTE_OP_STALE_MS` | Compute | `max(30m, 4x max compute timeout)` | Shared stale window for compute op replacement |
|
||||
| `WHISPER_MODEL_BASE_URL` | Compute model source | onnx-community default | Override Whisper ONNX model base URL |
|
||||
| `PDF_LAYOUT_MODEL_BASE_URL` | Compute model source | PP-DocLayoutV3 default | Override PDF layout ONNX model base URL |
|
||||
| `COMPUTE_WORKER_URL` | External compute mode | unset | Set only for standalone external worker mode |
|
||||
| `COMPUTE_WORKER_TOKEN` | External compute mode | unset | Required for standalone external worker auth |
|
||||
| `FFMPEG_BIN` | Audio runtime | auto-detected (`ffmpeg-static`) | Override ffmpeg binary path |
|
||||
| `DISABLE_AUTH_RATE_LIMIT` | Auth request throttling | `false` | Set `true` to disable Better Auth request rate limiting |
|
||||
| `ENABLE_TEST_NAMESPACE` | Testing/CI | unset | Honor `x-openreader-test-namespace` header in production builds |
|
||||
| `RUN_DRIZZLE_MIGRATIONS` | DB migrations | `true` | Set `false` to skip startup Drizzle migrations |
|
||||
| `RUN_FS_MIGRATIONS` | Storage migrations | `true` | Set `false` to skip startup filesystem -> S3/DB migration pass |
|
||||
| `RUNTIME_SEED_JSON_PATH` | Runtime JSON seed | unset | Absolute path to first-boot JSON seed document |
|
||||
| `RUNTIME_SEED_JSON` | Runtime JSON seed | unset | Inline first-boot JSON seed document |
|
||||
|
||||
## Runtime Logging
|
||||
|
||||
### LOG_FORMAT
|
||||
|
||||
Controls log output format for server-side Pino loggers.
|
||||
|
||||
- Default: `pretty`
|
||||
- Allowed values: `pretty`, `json`
|
||||
- Applies to app server and compute worker
|
||||
|
||||
### LOG_LEVEL
|
||||
|
||||
App server log level.
|
||||
|
||||
- Default: `info`
|
||||
|
||||
## TTS Provider and Request Behavior
|
||||
|
||||
### API_BASE
|
||||
|
||||
Optional first-boot bootstrap base URL for the auto-created `default-openai` shared provider.
|
||||
|
||||
- Example: `http://host.docker.internal:8880/v1`
|
||||
- Read only for provider bootstrap when shared providers are empty and `API_KEY` is set.
|
||||
- After bootstrap, provider configuration is DB-backed and managed in **Settings → Admin → Shared providers**.
|
||||
|
||||
### API_KEY
|
||||
|
||||
Optional first-boot bootstrap API key for the auto-created `default-openai` shared provider.
|
||||
|
||||
- Read only for provider bootstrap when shared providers are empty.
|
||||
- Stored encrypted at rest after bootstrap.
|
||||
- After bootstrap, provider configuration is DB-backed and managed in **Settings → Admin → Shared providers**.
|
||||
|
||||
### TTS Daily Rate Limiting (Runtime Settings)
|
||||
|
||||
Managed as runtime config in **Settings → Admin → Site features**.
|
||||
|
||||
- `disableTtsRateLimit` default: `true` (daily TTS limits disabled)
|
||||
- `ttsDailyLimitAnonymous` default: `50000`
|
||||
- `ttsDailyLimitAuthenticated` default: `500000`
|
||||
- `ttsIpDailyLimitAnonymous` default: `100000`
|
||||
- `ttsIpDailyLimitAuthenticated` default: `1000000`
|
||||
|
||||
### TTS Upstream Settings (Runtime Settings)
|
||||
|
||||
Managed as runtime config in **Settings → Admin → Site features → TTS upstream**.
|
||||
|
||||
- `ttsUpstreamMaxRetries` default: `2`
|
||||
- `ttsUpstreamTimeoutMs` default: `285000`
|
||||
- `ttsCacheMaxSizeBytes` default: `268435456` (256 MB)
|
||||
- `ttsCacheTtlMs` default: `1800000` (30 minutes)
|
||||
|
||||
There are no dedicated env vars for these runtime settings.
|
||||
|
||||
## Auth and Identity
|
||||
|
||||
### BASE_URL
|
||||
|
||||
Required external base URL for this OpenReader instance.
|
||||
|
||||
- Required at startup
|
||||
- Example: `http://localhost:3003` or `https://reader.example.com`
|
||||
|
||||
### AUTH_SECRET
|
||||
|
||||
Required secret key used by auth/session handling.
|
||||
|
||||
- Required at startup
|
||||
- Generate with `openssl rand -hex 32`
|
||||
|
||||
### AUTH_TRUSTED_ORIGINS
|
||||
|
||||
Additional allowed origins for auth requests.
|
||||
|
||||
- Comma-separated list
|
||||
- `BASE_URL` origin is trusted automatically
|
||||
|
||||
### USE_ANONYMOUS_AUTH_SESSIONS
|
||||
|
||||
Controls whether auth-enabled deployments can create/use anonymous sessions.
|
||||
|
||||
- Default: `false`
|
||||
|
||||
### GITHUB_CLIENT_ID
|
||||
|
||||
GitHub OAuth client ID.
|
||||
|
||||
- Set with `GITHUB_CLIENT_SECRET`
|
||||
|
||||
### GITHUB_CLIENT_SECRET
|
||||
|
||||
GitHub OAuth client secret.
|
||||
|
||||
- Set with `GITHUB_CLIENT_ID`
|
||||
|
||||
### ADMIN_EMAILS
|
||||
|
||||
Comma-separated list of email addresses auto-promoted to admin.
|
||||
|
||||
- Requires auth to be enabled
|
||||
- Admins can manage shared providers and runtime site features in-app
|
||||
|
||||
## Database and Object Blob Storage
|
||||
|
||||
### POSTGRES_URL
|
||||
|
||||
Switches metadata/auth storage from SQLite to Postgres.
|
||||
|
||||
- Unset: SQLite at `docstore/sqlite3.db`
|
||||
- Set: Postgres mode
|
||||
|
||||
### USE_EMBEDDED_WEED_MINI
|
||||
|
||||
Controls embedded SeaweedFS startup.
|
||||
|
||||
- Default behavior: treated as enabled when unset
|
||||
- Set `false` to rely on external S3-compatible storage
|
||||
|
||||
### WEED_MINI_DIR
|
||||
|
||||
Data directory for embedded SeaweedFS (`weed mini`).
|
||||
|
||||
- Default: `docstore/seaweedfs`
|
||||
|
||||
### WEED_MINI_WAIT_SEC
|
||||
|
||||
Max wait time for embedded SeaweedFS startup.
|
||||
|
||||
- Default: `20`
|
||||
|
||||
### S3_ACCESS_KEY_ID
|
||||
|
||||
S3 access key.
|
||||
|
||||
- Optional in embedded mode (auto-generated when unset)
|
||||
- Required for external S3 mode
|
||||
|
||||
### S3_SECRET_ACCESS_KEY
|
||||
|
||||
S3 secret key.
|
||||
|
||||
- Optional in embedded mode (auto-generated when unset)
|
||||
- Required for external S3 mode
|
||||
|
||||
### S3_BUCKET
|
||||
|
||||
S3 bucket name.
|
||||
|
||||
- Embedded default: `openreader-documents`
|
||||
- Required for external S3 mode
|
||||
|
||||
### S3_REGION
|
||||
|
||||
S3 region.
|
||||
|
||||
- Embedded default: `us-east-1`
|
||||
- Required for external S3 mode
|
||||
|
||||
### S3_ENDPOINT
|
||||
|
||||
Custom endpoint for S3-compatible providers.
|
||||
|
||||
- Optional for AWS
|
||||
- Typical for MinIO/SeaweedFS/R2
|
||||
|
||||
### S3_FORCE_PATH_STYLE
|
||||
|
||||
Force path-style S3 URLs.
|
||||
|
||||
- Embedded default: `true`
|
||||
|
||||
### S3_PREFIX
|
||||
|
||||
Object key prefix.
|
||||
|
||||
- Default: `openreader`
|
||||
|
||||
## Library Import
|
||||
|
||||
### IMPORT_LIBRARY_DIR
|
||||
|
||||
Single library source directory.
|
||||
|
||||
### IMPORT_LIBRARY_DIRS
|
||||
|
||||
Multiple library roots.
|
||||
|
||||
- Supports comma, colon, or semicolon-separated values
|
||||
|
||||
## Compute Worker and Model Configuration
|
||||
|
||||
### EMBEDDED_COMPUTE_WORKER_PORT
|
||||
|
||||
Embedded compute worker port.
|
||||
|
||||
- Default: `8081`
|
||||
|
||||
### EMBEDDED_NATS_PORT
|
||||
|
||||
Embedded NATS client port.
|
||||
|
||||
- Default: `4222`
|
||||
|
||||
### EMBEDDED_NATS_MONITOR_PORT
|
||||
|
||||
Embedded NATS monitor port.
|
||||
|
||||
- Default: `8222`
|
||||
|
||||
### EMBEDDED_NATS_STORE_DIR
|
||||
|
||||
Embedded NATS JetStream data directory.
|
||||
|
||||
- Default: `docstore/nats/jetstream`
|
||||
|
||||
### NATS_URL
|
||||
|
||||
NATS URL used by compute services.
|
||||
|
||||
- Embedded startup default: `nats://127.0.0.1:4222`
|
||||
|
||||
### COMPUTE_LOG_LEVEL
|
||||
|
||||
Compute worker log level.
|
||||
|
||||
- Default: `info`
|
||||
|
||||
### COMPUTE_JOB_CONCURRENCY
|
||||
|
||||
Max concurrent compute jobs per worker.
|
||||
|
||||
- Default: `1`
|
||||
|
||||
### COMPUTE_WHISPER_TIMEOUT_MS
|
||||
|
||||
Whisper alignment timeout budget.
|
||||
|
||||
- Default: `30000`
|
||||
|
||||
### COMPUTE_PDF_TIMEOUT_MS
|
||||
|
||||
PDF parse timeout budget.
|
||||
|
||||
- Default: `300000`
|
||||
|
||||
### COMPUTE_OP_STALE_MS
|
||||
|
||||
Stale operation window before worker/app cleanup logic can replace an op.
|
||||
|
||||
- Default: `max(30m, 4x max compute timeout)`
|
||||
|
||||
### WHISPER_MODEL_BASE_URL
|
||||
|
||||
Base URL for Whisper ONNX model downloads.
|
||||
|
||||
### PDF_LAYOUT_MODEL_BASE_URL
|
||||
|
||||
Base URL for PDF layout model downloads.
|
||||
|
||||
### COMPUTE_WORKER_URL
|
||||
|
||||
External compute worker URL.
|
||||
|
||||
- Leave unset for embedded worker mode
|
||||
|
||||
### COMPUTE_WORKER_TOKEN
|
||||
|
||||
Shared token for app-to-external-worker requests.
|
||||
|
||||
## Compute PDF Parsing Rate Limiting (Runtime Settings)
|
||||
|
||||
Managed as runtime config in **Settings → Admin → Site features**.
|
||||
|
||||
- `disableComputeRateLimit` default: `true`
|
||||
- `computeParseBurstMax` default: `8`
|
||||
- `computeParseBurstWindowSec` default: `60`
|
||||
- `computeParseSustainedMax` default: `24`
|
||||
- `computeParseSustainedWindowSec` default: `600`
|
||||
- `maxUploadMb` default: `200`
|
||||
|
||||
There are no dedicated env vars for these runtime settings.
|
||||
|
||||
## Audio Runtime
|
||||
|
||||
### FFMPEG_BIN
|
||||
|
||||
Override ffmpeg binary path used for audio processing.
|
||||
|
||||
- Used by audiobook processing routes and compute worker Whisper audio decode.
|
||||
|
||||
## Testing and CI
|
||||
|
||||
### DISABLE_AUTH_RATE_LIMIT
|
||||
|
||||
Disables Better Auth request rate limiting.
|
||||
|
||||
- Default: `false`
|
||||
|
||||
### ENABLE_TEST_NAMESPACE
|
||||
|
||||
Enables the `x-openreader-test-namespace` header path in production builds.
|
||||
|
||||
## Migration Controls
|
||||
|
||||
### RUN_DRIZZLE_MIGRATIONS
|
||||
|
||||
Controls startup Drizzle schema migrations.
|
||||
|
||||
- Default: `true`
|
||||
- Set `false` to skip startup migration run
|
||||
|
||||
### RUN_FS_MIGRATIONS
|
||||
|
||||
Controls startup filesystem-to-S3/DB migration pass.
|
||||
|
||||
- Default: `true`
|
||||
- Set `false` to skip startup storage migration run
|
||||
|
||||
## Runtime JSON Seed (v4)
|
||||
|
||||
### RUNTIME_SEED_JSON_PATH
|
||||
|
||||
Path-based first-boot seed document.
|
||||
|
||||
- If both `RUNTIME_SEED_JSON_PATH` and `RUNTIME_SEED_JSON` are set, path wins.
|
||||
- Value must point to a JSON file readable by the app process.
|
||||
|
||||
### RUNTIME_SEED_JSON
|
||||
|
||||
Inline first-boot seed document.
|
||||
|
||||
- Used only when `RUNTIME_SEED_JSON_PATH` is unset.
|
||||
- Must be a JSON object with `version: 1`.
|
||||
|
||||
Supported top-level keys:
|
||||
|
||||
- `version` (required, must be `1`)
|
||||
- `runtimeConfig` (optional object, strict-validated against runtime schema)
|
||||
- `providers` (optional array of shared provider seed entries)
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"runtimeConfig": {
|
||||
"enableUserSignups": true,
|
||||
"restrictUserApiKeys": true,
|
||||
"defaultTtsProvider": "custom-openai",
|
||||
"enableTtsProvidersTab": true,
|
||||
"enableAudiobookExport": true,
|
||||
"enableDocxConversion": true,
|
||||
"enableDestructiveDeleteActions": true,
|
||||
"showAllProviderModels": true,
|
||||
"disableTtsRateLimit": true,
|
||||
"ttsDailyLimitAnonymous": 50000,
|
||||
"ttsDailyLimitAuthenticated": 500000,
|
||||
"ttsIpDailyLimitAnonymous": 100000,
|
||||
"ttsIpDailyLimitAuthenticated": 1000000,
|
||||
"ttsCacheMaxSizeBytes": 268435456,
|
||||
"ttsCacheTtlMs": 1800000,
|
||||
"ttsUpstreamMaxRetries": 2,
|
||||
"ttsUpstreamTimeoutMs": 285000,
|
||||
"disableComputeRateLimit": true,
|
||||
"computeParseBurstMax": 8,
|
||||
"computeParseBurstWindowSec": 60,
|
||||
"computeParseSustainedMax": 24,
|
||||
"computeParseSustainedWindowSec": 600,
|
||||
"maxUploadMb": 200,
|
||||
"changelogFeedUrl": "https://docs.openreader.richardr.dev/changelog/manifest.json"
|
||||
},
|
||||
"providers": [
|
||||
{
|
||||
"slug": "default-openai",
|
||||
"displayName": "Default (seeded)",
|
||||
"providerType": "custom-openai",
|
||||
"baseUrl": "http://localhost:8880/v1",
|
||||
"apiKey": "api_key_optional",
|
||||
"defaultModel": "kokoro",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Provider fallback behavior:
|
||||
|
||||
- If the JSON seed includes `providers` (including an empty array), `API_BASE` / `API_KEY` fallback is skipped.
|
||||
- If the JSON seed does not include a `providers` key, the legacy `API_BASE` / `API_KEY` bootstrap fallback can still create `default-openai` when provider rows are empty.
|
||||
|
||||
Precedence summary:
|
||||
|
||||
- Runtime reads: admin DB runtime rows override built-in defaults.
|
||||
- Seed input (`RUNTIME_SEED_JSON*`) only populates missing runtime rows on first boot; it does not overwrite existing/admin-edited rows.
|
||||
- Provider bootstrap order: JSON `providers` section > `API_BASE`/`API_KEY` fallback > no provider bootstrap.
|
||||
|
||||
## Related
|
||||
|
||||
- [Admin Panel](../configure/admin-panel)
|
||||
- [TTS Providers](../configure/tts-providers)
|
||||
- [Local Development](../deploy/local-development)
|
||||
- [Vercel Deployment](../deploy/vercel-deployment)
|
||||
70
docs-site/versioned_docs/version-v4.0.1/reference/stack.md
Normal file
70
docs-site/versioned_docs/version-v4.0.1/reference/stack.md
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: Stack
|
||||
---
|
||||
|
||||
## Framework
|
||||
|
||||
- [Next.js](https://nextjs.org/) 15 (App Router, Turbopack in dev)
|
||||
- [React](https://react.dev/) 19
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [pnpm](https://pnpm.io/) workspaces monorepo
|
||||
|
||||
## Containerization and runtime
|
||||
|
||||
- [Docker](https://www.docker.com/) (linux/amd64 and linux/arm64)
|
||||
- Shared entrypoint that runs DB migrations by default and can bootstrap embedded SeaweedFS before app startup
|
||||
|
||||
## Next.js client
|
||||
|
||||
- UI: [Tailwind CSS](https://tailwindcss.com), [Headless UI](https://headlessui.com), [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin)
|
||||
- Interactions: `react-dnd`, `react-dropzone`
|
||||
- Server state: [TanStack Query](https://tanstack.com/query) (React Query v5)
|
||||
- Authentication: [Better Auth](https://www.better-auth.com/) client SDK
|
||||
- Local storage/cache: [Dexie.js](https://dexie.org/) (IndexedDB)
|
||||
- Audio playback: [Howler.js](https://howlerjs.com/)
|
||||
- Notifications: `react-hot-toast`
|
||||
- Document rendering:
|
||||
- PDF: [react-pdf](https://github.com/wojtekmaj/react-pdf), [pdf.js](https://mozilla.github.io/pdf.js/)
|
||||
- EPUB: [react-reader](https://github.com/gerhardsletten/react-reader), [epubjs](https://github.com/futurepress/epub.js/)
|
||||
- Markdown/Text: [react-markdown](https://github.com/remarkjs/react-markdown), [remark-gfm](https://github.com/remarkjs/remark-gfm)
|
||||
- Text preprocessing/matching: [compromise](https://github.com/spencermountain/compromise), [cmpstr](https://github.com/remsky/cmpstr)
|
||||
- Analytics: [Vercel Analytics](https://vercel.com/analytics)
|
||||
|
||||
## Next.js server
|
||||
|
||||
- APIs: Route Handlers for sync, blob/content access, migrations, audiobook export, TTS/Whisper proxying
|
||||
- State sync: request-based today (not realtime push updates)
|
||||
- Authentication: [Better Auth](https://www.better-auth.com/) server handlers/adapters with anonymous session support
|
||||
- Metadata DB: [Drizzle ORM](https://orm.drizzle.team/) with SQLite (`better-sqlite3`) by default and optional Postgres (`pg`)
|
||||
- App tables are manually maintained in Drizzle schema files
|
||||
- Auth tables are auto-generated by the [Better Auth](https://www.better-auth.com/) CLI and migrated alongside app tables via Drizzle
|
||||
- Blob storage: embedded [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (`weed mini`) by default, or external S3-compatible storage via AWS SDK v3
|
||||
- TTS providers: OpenAI-compatible API (`openai` SDK), [Replicate](https://replicate.com/) (`replicate` client), DeepInfra, and custom OpenAI-compatible endpoints — credentials are encrypted at rest
|
||||
- Audio pipeline: [ffmpeg](https://ffmpeg.org/) (`ffmpeg-static`) for audiobook assembly, `archiver` for export packaging
|
||||
- Utilities: `lru-cache` for in-process caching, `fast-xml-parser` for EPUB/XML parsing, `uuid` for identifier generation, `zod` for schema validation
|
||||
|
||||
## External compute worker (optional)
|
||||
|
||||
Monorepo packages under `compute/`:
|
||||
|
||||
- **`@openreader/compute-core`** — ONNX runtime lifecycle, model management, and inference logic shared by compute worker runtime + app/worker contracts
|
||||
- ONNX runtime: `onnxruntime-node` with `@huggingface/tokenizers`
|
||||
- Whisper alignment: `onnx-community/whisper-base_timestamped` (q4) for word-level timestamps
|
||||
- PDF layout: `Bei0001/PP-DocLayoutV3-ONNX` for document block detection and layout parsing
|
||||
- PDF rendering: `pdfjs-dist`, `@napi-rs/canvas` for server-side page rasterization
|
||||
- Utilities: `jszip`, `ffmpeg-static`
|
||||
- **`@openreader/compute-worker`** — standalone Node.js worker service
|
||||
- HTTP server: [Fastify](https://fastify.dev/) v5
|
||||
- Job queue + state: [NATS](https://nats.io/) JetStream WorkQueue pull consumers + NATS KV (`jobs.whisper`, `jobs.layout`)
|
||||
- Storage: AWS SDK v3 S3 client for reading/writing blobs
|
||||
- Logging: [Pino](https://getpino.io/)
|
||||
- Validation: [Zod](https://zod.dev/)
|
||||
- Heavy compute is worker-backed via `COMPUTE_WORKER_URL` + `COMPUTE_WORKER_TOKEN` (remote queue via HTTP + NATS)
|
||||
|
||||
## Tooling and testing
|
||||
|
||||
- ESLint
|
||||
- TypeScript
|
||||
- [Playwright](https://playwright.dev/) end-to-end tests
|
||||
- Drizzle migration/generation scripts
|
||||
- [Docusaurus](https://docusaurus.io/) documentation site (`docs-site/`)
|
||||
78
docs-site/versioned_sidebars/version-v4.0.1-sidebars.json
Normal file
78
docs-site/versioned_sidebars/version-v4.0.1-sidebars.json
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"tutorialSidebar": [
|
||||
"intro",
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "docker-quick-start",
|
||||
"label": "🐳 Docker Quick Start"
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "⚙️ Configure",
|
||||
"items": [
|
||||
{
|
||||
"type": "category",
|
||||
"label": "🔊 TTS Providers",
|
||||
"link": {
|
||||
"type": "doc",
|
||||
"id": "configure/tts-providers"
|
||||
},
|
||||
"items": [
|
||||
"configure/tts-provider-guides/kokoro-fastapi",
|
||||
"configure/tts-provider-guides/kitten-tts-fastapi",
|
||||
"configure/tts-provider-guides/orpheus-fastapi",
|
||||
"configure/tts-provider-guides/replicate",
|
||||
"configure/tts-provider-guides/deepinfra",
|
||||
"configure/tts-provider-guides/openai",
|
||||
"configure/tts-provider-guides/other"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "configure/auth",
|
||||
"label": "🔐 Auth"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "configure/admin-panel",
|
||||
"label": "🛡️ Admin Panel"
|
||||
},
|
||||
{
|
||||
"type": "doc",
|
||||
"id": "configure/server-library-import",
|
||||
"label": "📥 Server Library Import"
|
||||
},
|
||||
"configure/tts-rate-limiting",
|
||||
"configure/database",
|
||||
"configure/object-blob-storage",
|
||||
"configure/migrations"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "🚀 Deploy",
|
||||
"items": [
|
||||
"deploy/local-development",
|
||||
"deploy/compute-worker",
|
||||
"deploy/vercel-deployment"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "Reference",
|
||||
"items": [
|
||||
"reference/environment-variables",
|
||||
"reference/stack"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "category",
|
||||
"label": "About",
|
||||
"items": [
|
||||
"about/support-and-contributing",
|
||||
"about/acknowledgements",
|
||||
"about/license"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
[
|
||||
"v4.0.1",
|
||||
"v4.0.0",
|
||||
"v3.0.0",
|
||||
"v2.2.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue