diff --git a/docs-site/docs/configure/admin-panel.md b/docs-site/docs/configure/admin-panel.md new file mode 100644 index 0000000..41fd0c2 --- /dev/null +++ b/docs-site/docs/configure/admin-panel.md @@ -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. diff --git a/docs-site/docs/configure/auth.md b/docs-site/docs/configure/auth.md index c89400c..fcedf3f 100644 --- a/docs-site/docs/configure/auth.md +++ b/docs-site/docs/configure/auth.md @@ -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) diff --git a/docs-site/docs/configure/tts-provider-guides/deepinfra.md b/docs-site/docs/configure/tts-provider-guides/deepinfra.md index d1d6426..f52868f 100644 --- a/docs-site/docs/configure/tts-provider-guides/deepinfra.md +++ b/docs-site/docs/configure/tts-provider-guides/deepinfra.md @@ -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 diff --git a/docs-site/docs/configure/tts-provider-guides/kitten-tts-fastapi.md b/docs-site/docs/configure/tts-provider-guides/kitten-tts-fastapi.md index 427a41c..02b6226 100644 --- a/docs-site/docs/configure/tts-provider-guides/kitten-tts-fastapi.md +++ b/docs-site/docs/configure/tts-provider-guides/kitten-tts-fastapi.md @@ -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 diff --git a/docs-site/docs/configure/tts-provider-guides/kokoro-fastapi.md b/docs-site/docs/configure/tts-provider-guides/kokoro-fastapi.md index 10271ff..7a3fc2c 100644 --- a/docs-site/docs/configure/tts-provider-guides/kokoro-fastapi.md +++ b/docs-site/docs/configure/tts-provider-guides/kokoro-fastapi.md @@ -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 diff --git a/docs-site/docs/configure/tts-provider-guides/openai.md b/docs-site/docs/configure/tts-provider-guides/openai.md index b5cf6fe..e99e9ba 100644 --- a/docs-site/docs/configure/tts-provider-guides/openai.md +++ b/docs-site/docs/configure/tts-provider-guides/openai.md @@ -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 diff --git a/docs-site/docs/configure/tts-provider-guides/orpheus-fastapi.md b/docs-site/docs/configure/tts-provider-guides/orpheus-fastapi.md index fef9a7c..13e39ab 100644 --- a/docs-site/docs/configure/tts-provider-guides/orpheus-fastapi.md +++ b/docs-site/docs/configure/tts-provider-guides/orpheus-fastapi.md @@ -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 diff --git a/docs-site/docs/configure/tts-provider-guides/other.md b/docs-site/docs/configure/tts-provider-guides/other.md index c509872..bcb3a0a 100644 --- a/docs-site/docs/configure/tts-provider-guides/other.md +++ b/docs-site/docs/configure/tts-provider-guides/other.md @@ -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`. diff --git a/docs-site/docs/configure/tts-provider-guides/replicate.md b/docs-site/docs/configure/tts-provider-guides/replicate.md index 14cdaf6..01cfc31 100644 --- a/docs-site/docs/configure/tts-provider-guides/replicate.md +++ b/docs-site/docs/configure/tts-provider-guides/replicate.md @@ -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 diff --git a/docs-site/docs/configure/tts-providers.md b/docs-site/docs/configure/tts-providers.md index 89bd942..7a03e5d 100644 --- a/docs-site/docs/configure/tts-providers.md +++ b/docs-site/docs/configure/tts-providers.md @@ -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) diff --git a/docs-site/docs/deploy/local-development.md b/docs-site/docs/deploy/local-development.md index e8806fa..7fc484a 100644 --- a/docs-site/docs/deploy/local-development.md +++ b/docs-site/docs/deploy/local-development.md @@ -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. ``` @@ -207,6 +209,20 @@ BASE_URL=http://localhost:3003 AUTH_SECRET= # Optional when you need multiple local origins: # AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 +``` + + + + +```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= +# Comma-separated emails to auto-promote to admin on signin. +ADMIN_EMAILS=you@example.com ``` @@ -228,11 +244,20 @@ S3_SECRET_ACCESS_KEY=your-secret-key +:::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). diff --git a/docs-site/docs/deploy/vercel-deployment.md b/docs-site/docs/deploy/vercel-deployment.md index c5a7e0c..92e1f14 100644 --- a/docs-site/docs/deploy/vercel-deployment.md +++ b/docs-site/docs/deploy/vercel-deployment.md @@ -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. diff --git a/docs-site/docs/docker-quick-start.md b/docs-site/docs/docker-quick-start.md index c885897..6af01e8 100644 --- a/docs-site/docs/docker-quick-start.md +++ b/docs-site/docs/docker-quick-start.md @@ -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. @@ -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. @@ -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. :::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) diff --git a/docs-site/docs/reference/environment-variables.md b/docs-site/docs/reference/environment-variables.md index 682a357..43b1f36 100644 --- a/docs-site/docs/reference/environment-variables.md +++ b/docs-site/docs/reference/environment-variables.md @@ -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` diff --git a/docs-site/sidebars.ts b/docs-site/sidebars.ts index e285ea5..b433750 100644 --- a/docs-site/sidebars.ts +++ b/docs-site/sidebars.ts @@ -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',