docs(admin,config,tts): document admin panel for runtime TTS provider and feature config

Add a dedicated Admin Panel documentation page detailing management of shared TTS providers and site features via the admin UI. Update all TTS provider guides, environment variable references, and deployment docs to clarify the new runtime configuration model: environment variables serve as first-boot seeds only, with ongoing management handled through the admin interface. Revise sidebars and cross-links to include the new admin panel docs and clarify the distinction between legacy env-based and admin-managed configuration.
This commit is contained in:
Richard R 2026-05-12 22:39:37 -06:00
parent 876ca7d774
commit dae97b2afc
15 changed files with 389 additions and 127 deletions

View file

@ -0,0 +1,111 @@
---
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 `NEXT_PUBLIC_*` build-time 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 from `NEXT_PUBLIC_DEFAULT_TTS_MODEL` if set
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.
## 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). |
| `defaultTtsModel` | Default model id new users start with. |
| `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. |
| `showAllDeepInfraModels` | Show the full DeepInfra catalog vs. the Kokoro-only subset. |
| `enableWordHighlight` | Enable whisper.cpp word-by-word highlighting during TTS playback. |
| `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). |
Each row shows a source badge:
- **from env** — the value was migrated from the corresponding `NEXT_PUBLIC_*` env var on first boot. Editing it in the UI flips the source to **admin**.
- **admin** — explicit admin override. Use **Reset** on the row to clear it back to the env-default state.
- **default** — neither env nor admin set; uses the built-in default.
:::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.
:::
## Migrating off env vars
The future-direction goal is to remove `NEXT_PUBLIC_*` / `API_KEY` / `API_BASE` from your `.env` entirely. To do that safely:
1. Deploy this version with your existing env values in place.
2. Boot the app once. Open Settings → Admin and verify:
- Each `NEXT_PUBLIC_*` setting appears as **from env**.
- A `default-openai` row exists in **Shared providers** (if you had `API_KEY` set).
3. Remove the env vars from your `.env`.
4. Redeploy. Behavior is unchanged — the DB is now the source of truth.
You can keep the env vars indefinitely if you prefer; they're only read on the first boot when the corresponding DB row is absent, so there's no harm in leaving them around.
## 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`. This means rotating `AUTH_SECRET` will invalidate all stored admin keys — re-enter them via the admin UI after rotating.
- 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.
## 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` and the legacy flags that the admin UI replaces.

View file

@ -12,6 +12,29 @@ This page covers application-level configuration for provider access and authent
- Anonymous auth sessions are disabled by default.
- Set `USE_ANONYMOUS_AUTH_SESSIONS=true` to enable anonymous session flows.
## Runtime modes
OpenReader effectively has three common runtime modes:
- **Auth disabled** (`BASE_URL` or `AUTH_SECRET` unset): no admin panel. Shared providers can still exist via first-boot seeding (`API_KEY`/`API_BASE`), but you cannot manage them in-app.
- **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
When auth is enabled, 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 `NEXT_PUBLIC_*` build-time flags (default TTS provider/model, word highlighting, 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.
@ -22,6 +45,7 @@ This page covers application-level configuration for provider access and authent
## 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)

View file

@ -6,22 +6,28 @@ Use DeepInfra's hosted TTS models as your provider.
## Setup
**Environment variables (recommended for deployment):**
**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
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
```
**Or in-app via Settings → TTS Provider:**
**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.
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## Notes

View file

@ -16,7 +16,14 @@ docker run -it --rm \
## Connect to OpenReader
**Environment variables (recommended for deployment):**
**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
@ -31,7 +38,7 @@ API_BASE=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).
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## References

View file

@ -44,7 +44,14 @@ docker run --name kokoro-tts \
## Connect to OpenReader
**Environment variables (recommended for deployment):**
**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
@ -59,7 +66,7 @@ API_BASE=http://kokoro-tts:8880/v1
3. Leave `API_KEY` blank unless your deployment requires one.
4. Choose model `Kokoro`.
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## References

View file

@ -6,22 +6,28 @@ Use the OpenAI TTS API as your provider.
## Setup
**Environment variables (recommended for deployment):**
**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-...
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=openai
```
**Or in-app via Settings → TTS Provider:**
**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.
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## Notes

View file

@ -10,7 +10,14 @@ Refer to the upstream repository for Docker instructions: [Lex-au/Orpheus-FastAP
## Connect to OpenReader
**Environment variables (recommended for deployment):**
**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
@ -25,7 +32,7 @@ API_BASE=http://orpheus:8000/v1
3. Leave `API_KEY` blank unless your deployment requires one.
4. Choose model `Orpheus` (or the model your deployment exposes).
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## References

View file

@ -15,7 +15,14 @@ Known compatible implementations: [Kokoro-FastAPI](./kokoro-fastapi), [KittenTTS
## Setup
**Environment variables (recommended for deployment):**
**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
@ -29,7 +36,7 @@ API_KEY=optional-key-if-required
3. Set `API_KEY` if your service requires authentication.
4. Choose a model and voice supported by your backend.
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
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`.

View file

@ -6,21 +6,27 @@ Use Replicate's hosted TTS models as your provider.
## Setup
**Environment variables (recommended for deployment):**
**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_...
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=replicate
NEXT_PUBLIC_DEFAULT_TTS_MODEL=alphanumericuser/kokoro-82m:89b6fa84e4fa2dd6bd3a96be3e1f12827a3516c9fda8fddbac7a0be131c9a6f5
```
**Or in-app via Settings -> TTS Provider:**
**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.
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
## Notes

View file

@ -2,14 +2,16 @@
title: TTS Providers
---
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. You choose your provider and credentials in one of two places:
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. There are three places provider configuration can live:
**Environment variables**: set in your `.env` or `docker-compose.yml` as server-level defaults. Applied when the user has no saved Settings.
**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).
**Settings modal** (Settings > TTS Provider): stored in the browser and sent with every TTS request. **Overrides env vars.**
**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`.
:::note
Set env vars as deployment-level defaults. Users (or you, in a single-user setup) can then change the provider, base URL, and API key from the Settings modal without redeploying. Clearing the Settings fields falls back to the env var defaults.
**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**. Legacy first-boot seed via `NEXT_PUBLIC_RESTRICT_USER_API_KEYS=false` is still supported for no-admin bootstrap flows.
:::
## Providers
@ -23,7 +25,7 @@ For `OpenAI`, `DeepInfra`, and `Replicate` you only need to supply an API key. F
## Built-in model catalogs
- **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` (or choose `Other` and enter any Replicate model ID, such as `owner/model` or `owner/model:version`)
- **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)
@ -50,5 +52,6 @@ TTS requests originate from the **Next.js server**, not the browser. `API_BASE`
## 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)

View file

@ -194,7 +194,9 @@ Use one of these `.env` mode templates:
```env
API_BASE=http://host.docker.internal:8880/v1
API_KEY=none
# Leave BASE_URL and AUTH_SECRET unset to keep auth disabled
# Leave BASE_URL and AUTH_SECRET unset to keep auth disabled.
# (Admin panel is unavailable without auth.)
# API_BASE/API_KEY seed a shared default provider if you want shared mode.
```
</TabItem>
@ -207,6 +209,20 @@ 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>
@ -228,11 +244,20 @@ S3_SECRET_ACCESS_KEY=your-secret-key
</TabItem>
</Tabs>
:::note Env vars vs. admin panel
On first boot, `API_KEY` / `API_BASE` and any `NEXT_PUBLIC_*` flags you've set get auto-seeded into the admin-managed runtime config (DB-backed, keys encrypted at rest). After that, the admin UI is authoritative and editing those 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** when auth/admin is enabled, or via legacy first-boot seed `NEXT_PUBLIC_RESTRICT_USER_API_KEYS=false` for no-admin bootstrap flows).
:::
:::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).

View file

@ -15,10 +15,10 @@ This guide covers deploying OpenReader to Vercel with external Postgres and S3-c
## 1. Environment Variables
Recommended production setup (auth enabled):
Recommended production setup (auth enabled, admin panel enabled):
```bash
API_KEY=your_replicate_key
# Infrastructure
POSTGRES_URL=postgres://...
USE_EMBEDDED_WEED_MINI=false
S3_ACCESS_KEY_ID=...
@ -26,44 +26,66 @@ S3_SECRET_ACCESS_KEY=...
S3_BUCKET=...
S3_REGION=us-east-1
S3_PREFIX=openreader
BASE_URL=https://your-app.vercel.app
AUTH_SECRET=...
# Optional client/runtime feature defaults:
NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=replicate
NEXT_PUBLIC_DEFAULT_TTS_MODEL=alphanumericuser/kokoro-82m:89b6fa84e4fa2dd6bd3a96be3e1f12827a3516c9fda8fddbac7a0be131c9a6f5
NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
# 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
# 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
```
:::info Production Configuration & Feature Flags
We recommend setting these defaults for a production-like environment:
- `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false`: Disables DOCX upload (requires external tools anyway)
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
- `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false`: Hides the Settings -> TTS Provider section
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=replicate`: Points default TTS to a scalable provider
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=alphanumericuser/kokoro-82m:89b6fa84e4fa2dd6bd3a96be3e1f12827a3516c9fda8fddbac7a0be131c9a6f5`: Uses a low-cost default model
- `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false`: Restricts usage to free models if no key is provided
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
:::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).
:::
## 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.
- `restrictUserApiKeys=true` to block user BYOK through the hosted server.
- `defaultTtsProvider=replicate` (or your preferred shared slug).
- `defaultTtsModel=alphanumericuser/kokoro-82m:89b6fa84e4fa2dd6bd3a96be3e1f12827a3516c9fda8fddbac7a0be131c9a6f5`.
- `showAllDeepInfraModels=false` if you want a narrower default catalog.
- `enableAudiobookExport=true`.
- `enableWordHighlight=false` unless your timestamp stack is configured.
## 3. Legacy first-boot seed (optional)
If you must pre-seed site features via environment variables, the legacy `NEXT_PUBLIC_*` seeds are still supported on first boot only. Prefer the admin panel for ongoing management.
See [Environment Variables](../reference/environment-variables#legacy-first-boot-runtime-seeds-optional) for the complete legacy seed list.
:::warning Auth recommendation
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET` — they are also required for the admin panel and for encrypting admin-stored TTS credentials. Running without auth is possible, but not recommended for public environments.
:::
:::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).
:::
## 2. FFmpeg packaging in Vercel functions
## 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:
@ -78,7 +100,7 @@ For all variables and defaults, see [Environment Variables](../reference/environ
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
## 3. Function memory sizing
## 6. Function memory sizing
FFmpeg workloads benefit from more memory/CPU. This repo includes:
@ -94,14 +116,12 @@ FFmpeg workloads benefit from more memory/CPU. This repo includes:
Adjust memory per route if your files are larger or your plan differs.
## 4. Runtime expectations and caveats
## 7. Runtime expectations and caveats
- Audiobook APIs require S3 configuration; otherwise they return `503`.
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
## 5. Smoke test after deploy
## 8. Smoke test after deploy
1. Upload and read a PDF/EPUB document.
2. Confirm sync/blob fetch works across refreshes/devices.

View file

@ -40,6 +40,7 @@ docker run --name openreader \
-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
```
@ -49,9 +50,9 @@ What this command enables:
- `-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.
- `-v /path/to/your/library:/app/docstore/library:ro`: mounts a read-only importable library source.
- `-e API_BASE=...`: sets the server-side default TTS endpoint OpenReader calls.
- `-e API_KEY=...`: sets the server-side default TTS API key (`none` is fine for local backends that do not require auth).
- `-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=...`: together they turn on auth/session mode for local sign-in flows.
- `-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">
@ -69,6 +70,7 @@ docker run --name openreader \
-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
```
@ -80,7 +82,8 @@ What this command enables:
- `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` still sets the default server-side TTS endpoint.
- `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>
@ -100,15 +103,17 @@ What this command enables:
- Fastest startup with no extra env vars.
- No persistent volume (`/app/docstore` stays container-local), so data is ephemeral unless you add a mount.
- Auth remains disabled because `BASE_URL` and `AUTH_SECRET` are not set.
- TTS endpoint/key are not preset server-side (`API_BASE`/`API_KEY` not set), so configure provider settings in the app UI.
- Auth remains disabled because `BASE_URL` and `AUTH_SECRET` are not set. The admin panel requires auth, so it's unavailable in this 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` to a TTS endpoint the container can reach (`host.docker.internal` works for host-local services).
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
- 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**.
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set. 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 in auth-enabled setups, toggle it off in **Settings → Admin → Site features**. Legacy first-boot seed via `NEXT_PUBLIC_RESTRICT_USER_API_KEYS=false` is still supported.
- Use a `/app/docstore` mount if you want data to survive container/image replacement.
- Startup automatically runs DB/storage migrations via the shared entrypoint.
:::
@ -123,9 +128,9 @@ If `8333` is not reachable from the browser, direct presigned access is unavaila
Visit [http://localhost:3003](http://localhost:3003) after startup.
- Set TTS provider and model in Settings
- Set TTS API base URL and API key if needed
- Select the model voice from the voice dropdown
- 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
@ -145,6 +150,7 @@ If you use a mounted volume for `/app/docstore`, your persisted data remains aft
:::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)

View file

@ -5,20 +5,18 @@ toc_max_heading_level: 3
This is the single reference page for OpenReader environment variables.
:::note Recommended configuration path
For auth-enabled deployments, use **Settings → Admin** as the primary source of truth for shared TTS providers and site features. Legacy env vars (`API_KEY`, `API_BASE`, and `NEXT_PUBLIC_*`) are optional first-boot seeds only.
:::
## Quick Reference Table
| Variable | Area | Default | When to set |
| --- | --- | --- | --- |
| `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION` | Client feature flags | `true` unless set to `false` | Set `false` to hide DOCX support |
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
| `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB` | Client feature flags | `true` unless set to `false` | Set `false` to hide the TTS Provider settings tab |
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
| `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS` | Client feature flags | `true` unless set to `false` | Set `false` to restrict DeepInfra models |
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
| `API_KEY` | TTS provider | `none` fallback in TTS route | Set when provider requires auth |
| `ADMIN_EMAILS` | Auth/Admin | empty | Comma-separated emails auto-promoted to admin (requires auth enabled) |
| `API_BASE` | Legacy bootstrap seed | none | Optional first-boot seed into `default-openai`; then manage in Settings → Admin → Shared providers |
| `API_KEY` | Legacy bootstrap seed | none | Optional first-boot seed into `default-openai`; then manage in Settings → Admin → Shared providers |
| `NEXT_PUBLIC_*` runtime seeds | Legacy bootstrap seed | varies | Optional first-boot seeds for site features; then manage in Settings → Admin → Site features |
| `TTS_CACHE_MAX_SIZE_BYTES` | TTS caching | `268435456` (256 MB) | Tune in-memory TTS cache size |
| `TTS_CACHE_TTL_MS` | TTS caching | `1800000` (30 min) | Tune in-memory TTS cache TTL |
| `TTS_MAX_RETRIES` | TTS retry | `2` | Tune retry attempts for upstream 429/5xx |
@ -62,21 +60,19 @@ This is the single reference page for OpenReader environment variables.
### API_BASE
Server-level default base URL for OpenAI-compatible TTS API requests.
Bootstrap base URL for the legacy OpenAI-compatible TTS endpoint.
- Example: `http://host.docker.internal:8880/v1`
- Used when no `API_BASE` is set in the user's Settings modal
- If the user sets `API_BASE` in **Settings → TTS Provider**, that value takes precedence
- Related docs: [TTS Providers](../configure/tts-providers)
- **Seeded on first boot** into the auto-created `default-openai` shared provider, then no longer read by the running app. Manage in **Settings → Admin → Shared providers** afterwards.
- Related docs: [Admin Panel](../configure/admin-panel), [TTS Providers](../configure/tts-providers)
### API_KEY
Server-level default API key for TTS provider requests.
Bootstrap API key for the legacy OpenAI-compatible TTS endpoint.
- Example: your provider token, or omit if the provider doesn't require auth
- Used when no `API_KEY` is set in the user's Settings modal
- If the user sets `API_KEY` in **Settings → TTS Provider**, that value takes precedence
- Related docs: [TTS Providers](../configure/tts-providers)
- **Seeded on first boot** into the auto-created `default-openai` shared provider (encrypted at rest), then no longer read by the running app. Manage in **Settings → Admin → Shared providers** afterwards.
- Related docs: [Admin Panel](../configure/admin-panel), [TTS Providers](../configure/tts-providers)
### TTS_CACHE_MAX_SIZE_BYTES
@ -211,6 +207,17 @@ Controls Better Auth rate limiting.
- This does not affect TTS character rate limiting
- Related docs: [Auth](../configure/auth)
### ADMIN_EMAILS
Comma-separated list of email addresses that are auto-promoted to admin.
- Default: empty (no admins)
- Requires auth to be enabled (`AUTH_SECRET` + `BASE_URL`).
- Matched emails get `user.is_admin = true` on every session resolution; removed emails are demoted on the next session resolve.
- Admins see a new **Admin** tab in Settings exposing shared TTS providers and site-wide feature toggles. Keys for shared providers are stored encrypted in the DB and never returned to the client.
- Example: `ADMIN_EMAILS=alice@example.com,bob@example.com`
- Related docs: [Admin Panel](../configure/admin-panel), [Auth](../configure/auth)
## Database and Object Blob Storage
### POSTGRES_URL
@ -351,66 +358,81 @@ Absolute path or executable name for the ffmpeg binary used by audiobook/process
- Resolution order: `FFMPEG_BIN` -> `ffmpeg-static`
- Example: `/var/task/node_modules/ffmpeg-static/ffmpeg`
## Client Runtime and Feature Flags
## Legacy First-Boot Runtime Seeds (optional)
These variables exist only as **first-boot seeds** for the admin-managed runtime config. Prefer changing site features from **Settings → Admin → Site features**. Keep these only when you need bootstrap defaults before the first admin login. See [Admin Panel](../configure/admin-panel) for migration behavior.
The values are SSR-injected via `window.__OPENREADER_RUNTIME_CONFIG__`, so admin edits take effect for all users on the next page load — no rebuild required (unlike the old `NEXT_PUBLIC_*` build-time pattern).
### NEXT_PUBLIC_ENABLE_DOCX_CONVERSION
Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
- Default: `true` (enabled)
- Set `false` to hide DOCX support in the upload UI
### NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS
Controls whether the "Delete all user docs" and other bulk-delete buttons are shown in Settings.
- Default: `true` (enabled)
- Set `false` to hide destructive actions (recommended for production)
Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
- Default: `true` (enabled)
- Runtime key: `enableDocxConversion`
### NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS
Controls whether the "Delete all user docs" and other bulk-delete buttons are shown in Settings.
- Default: `true` (enabled)
- Runtime key: `enableDestructiveDeleteActions`
### NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB
Controls whether the **TTS Provider** section appears in the Settings modal.
Controls whether the **TTS Provider** section appears in the user-facing Settings modal.
- Default: `true` (enabled)
- Set `false` to hide provider/model/API controls in Settings
- Useful when you want provider config locked to environment defaults
### NEXT_PUBLIC_DEFAULT_TTS_PROVIDER
Sets the default TTS provider for new users.
- Default: `custom-openai`
- Example values: `replicate`, `deepinfra`, `openai`, `custom-openai`
### NEXT_PUBLIC_DEFAULT_TTS_MODEL
Sets the default TTS model for new users.
- Default: `kokoro`
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
### NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS
Controls whether the DeepInfra model list shows all models or just the free tier when no API key is set.
- Default: `true` (show all)
- Set `false` to restrict to free tier models when no API key is provided
- Set `false` to hide provider/model/API controls in the per-user Settings modal (the admin panel is unaffected).
- Runtime key: `enableTtsProvidersTab`
### NEXT_PUBLIC_RESTRICT_USER_API_KEYS
Controls whether users can supply personal API keys/base URLs for built-in providers.
- Default: runtime-dependent
- When `true`, server routes only use admin-managed shared providers.
- When `false`, users can use per-user BYOK credentials for built-in providers.
- Runtime key: `restrictUserApiKeys`
### NEXT_PUBLIC_DEFAULT_TTS_PROVIDER
Sets the default TTS provider for new users.
- Default: `custom-openai`
- Example values: `replicate`, `deepinfra`, `openai`, `custom-openai`, or an admin-defined shared provider slug (e.g. `kokoro-prod`)
- Runtime key: `defaultTtsProvider`
### NEXT_PUBLIC_DEFAULT_TTS_MODEL
Sets the default TTS model for new users.
- Default: `kokoro`
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
- Runtime key: `defaultTtsModel`
### NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS
Controls whether the DeepInfra model list shows all models or just the free tier when no API key is set.
- Default: `true` (show all)
- Runtime key: `showAllDeepInfraModels`
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
Controls whether audiobook export UI/actions are shown in the client.
- Default behavior: enabled unless explicitly set to `false`
- Applies in both development and production
- Default: `true` (enabled)
- Affects export entry points in PDF/EPUB pages and document settings UI
- Runtime key: `enableAudiobookExport`
### NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT
Controls word-by-word highlighting UI and timestamp-alignment behavior.
- Default behavior: enabled unless explicitly set to `false`
- Applies in both development and production
- Default: `true` (enabled)
- Requires working timestamp generation (for example `WHISPER_CPP_BIN`)
- Affects:
- Word-highlight toggles in document settings
- Alignment requests during TTS playback
- Runtime key: `enableWordHighlight`

View file

@ -34,6 +34,11 @@ const sidebars: SidebarsConfig = {
id: 'configure/auth',
label: '🔐 Auth',
},
{
type: 'doc',
id: 'configure/admin-panel',
label: '🛡️ Admin Panel',
},
{
type: 'doc',
id: 'configure/server-library-import',