diff --git a/.env.example b/.env.example
index 69c8ed0..f9612d0 100644
--- a/.env.example
+++ b/.env.example
@@ -11,45 +11,25 @@
# NOTE: On first boot, the server auto-seeds these values into a "default-openai"
# admin-managed shared provider (DB-backed, encrypted at rest). After that, the
# admin UI is authoritative and changing these env vars has no effect. You may
-# remove them once the seed has run. See Settings → Admin → Shared providers.
+# remove them once the seed has run. Auth must be configured for this seed path.
+# See Settings → Admin → Shared providers.
API_BASE=http://localhost:8880/v1
API_KEY=api_key_optional
-# (Optional) TTS request/cache tuning (defaults shown below; leave unset to use defaults)
-# TTS_CACHE_MAX_SIZE_BYTES=268435456 # 256MB
-# TTS_CACHE_TTL_MS=1800000 # 30 minutes
-# TTS_MAX_RETRIES=2
-# TTS_RETRY_INITIAL_MS=250
-# TTS_RETRY_MAX_MS=2000
-# TTS_RETRY_BACKOFF=2
-# TTS_UPSTREAM_TIMEOUT_MS=285000 # 285 seconds
-
-# Auth configuration (recommended; required for admin features)
-# (Optional) Auth is only enabled when AUTH_SECRET and BASE_URL are set
+# Auth configuration (required in v4+)
BASE_URL=http://localhost:3003 # Externally facing URL for this app (set to LAN IP for access from other devices on the network)
AUTH_SECRET=some_random_secret_key # Generate with `openssl rand -hex 32`
AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 # Additional trusted origins (BASE_URL is always trusted)
-# (Optional) Allow anonymous auth sessions when auth is enabled (default: `false`)
+# (Optional) Allow anonymous auth sessions (default: `false`)
# USE_ANONYMOUS_AUTH_SESSIONS=false
# (Optional) Sign in w/ GitHub Configuration
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
-# (Optional) Disable Better Auth built-in rate limiting (useful for testing; default: `false`)
-# DISABLE_AUTH_RATE_LIMIT=false
-
-# (Optional) Honor the x-openreader-test-namespace header on a production build.
-# This is test/CI scaffolding and MUST stay unset on real deployments; it is set
-# automatically by the Playwright web server. Non-production builds honor it
-# without this flag.
-# ENABLE_TEST_NAMESPACE=false
# (Optional) Comma-separated list of emails that are auto-promoted to admin.
-# Admins see the "Admin" tab in Settings (TTS shared providers + site features).
-# Demotion is automatic: removing an email here demotes the user on next login.
-# Requires auth to be enabled (AUTH_SECRET + BASE_URL).
ADMIN_EMAILS=
-# (Optional) Backend DB used for server-side metadata (documents/audiobooks) and, when auth is enabled, auth tables.
+# (Optional) Backend DB used for server-side metadata (documents/audiobooks) and auth tables.
# Defaults to SQLite at docstore/sqlite3.db when not set.
# POSTGRES_URL=
@@ -74,60 +54,37 @@ S3_BUCKET=
# IMPORT_LIBRARY_DIR=
# IMPORT_LIBRARY_DIRS=
-# Compute
-# Embedded/local (default): leave COMPUTE_WORKER_URL empty.
-# External worker: set COMPUTE_WORKER_URL + COMPUTE_WORKER_TOKEN.
-# External worker split:
-# - App side (this root `.env`): set only routing/auth + shared timeout/stale overrides.
-# - Worker side (`compute/worker/.env*` or worker platform env): set NATS_*, S3_*, model base URLs, and worker tuning.
-# Details: docs/deploy/compute-worker
-# COMPUTE_WORKER_URL=http://localhost:8081
-# COMPUTE_WORKER_TOKEN=local-compute-token
-# Optional embedded startup controls:
+# Compute worker configuration
+# (Optional) Embedded compute worker (automatic startup) for local compute tasks (defaults shown below)
# EMBEDDED_COMPUTE_WORKER_PORT=8081
# EMBEDDED_NATS_PORT=4222
# EMBEDDED_NATS_MONITOR_PORT=8222
# EMBEDDED_NATS_STORE_DIR=docstore/nats/jetstream
-# `NATS_URL` here is for embedded startup only. In external worker mode, set `NATS_URL` on the worker service env.
# NATS_URL=nats://127.0.0.1:4222
-# Optional worker log level:
# COMPUTE_LOG_LEVEL=info
-# Optional shared compute tuning:
# COMPUTE_JOB_CONCURRENCY=1
# COMPUTE_WHISPER_TIMEOUT_MS=30000
# COMPUTE_PDF_TIMEOUT_MS=300000
# COMPUTE_OP_STALE_MS=1800000
-
-# Optional Whisper ONNX base URL override (must contain all expected files)
-# Default expected Whisper variant is q4:
-# - onnx/encoder_model_q4.onnx
-# - onnx/decoder_model_merged_q4.onnx
-# - onnx/decoder_with_past_model_q4.onnx
# WHISPER_MODEL_BASE_URL=https://huggingface.co/onnx-community/whisper-base_timestamped/resolve/main
-
-# Optional PDF layout ONNX base URL override (must contain all expected files)
# PDF_LAYOUT_MODEL_BASE_URL=https://huggingface.co/Bei0001/PP-DocLayoutV3-ONNX/resolve/main
+# (Optional) External compute worker config (see docs/deploy/compute-worker)
+# COMPUTE_WORKER_URL=http://localhost:8081
+# COMPUTE_WORKER_TOKEN=local-compute-token
-# (Optional) Override ffmpeg binary path used for audiobook processing
+# (Optional) Override ffmpeg binary path used for audio processing
# FFMPEG_BIN=
-# (Optional) Client feature flags — seeded into the admin-managed runtime
-# config on first boot, then ignored. Edit values from Settings → Admin →
-# Site features instead of redeploying. SSR-injected so they take effect
-# without rebuilding (unlike the old build-time public-env pattern).
-# RUNTIME_SEED_ENABLE_DOCX_CONVERSION=true
-# RUNTIME_SEED_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=true
-# RUNTIME_SEED_ENABLE_TTS_PROVIDERS_TAB=true
-# RUNTIME_SEED_ENABLE_USER_SIGNUPS=true
-# RUNTIME_SEED_RESTRICT_USER_API_KEYS=true
-# RUNTIME_SEED_DEFAULT_TTS_PROVIDER=custom-openai
-# RUNTIME_SEED_CHANGELOG_FEED_URL=https://docs.openreader.richardr.dev/changelog/manifest.json
-# RUNTIME_SEED_ENABLE_AUDIOBOOK_EXPORT=true
-# RUNTIME_SEED_DISABLE_TTS_LIMIT=true
-# RUNTIME_SEED_DISABLE_COMPUTE_LIMIT=true
+# (Optional) Test/dev overrides
+# DISABLE_AUTH_RATE_LIMIT=false
+# ENABLE_TEST_NAMESPACE=false
-# Migrations configuration
# (Optional) Skip automatic startup migrations when set to `false` (default: `true`)
# RUN_DRIZZLE_MIGRATIONS=true
# (Optional) Skip automatic filesystem->S3/DB migration pass when set to `false` (default: `true`)
# RUN_FS_MIGRATIONS=true
+
+# (Optional) v4 JSON seed for first-boot runtime config + shared providers.
+# If both are set, RUNTIME_SEED_JSON_PATH is used.
+# RUNTIME_SEED_JSON_PATH=/absolute/path/to/openreader-seed.json
+# RUNTIME_SEED_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}]}
diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
index 723e0eb..7ed850a 100644
--- a/.github/workflows/playwright.yml
+++ b/.github/workflows/playwright.yml
@@ -11,7 +11,10 @@ jobs:
runs-on: ubuntu-latest
env:
USE_EMBEDDED_WEED_MINI: true
+ ENABLE_TEST_NAMESPACE: true
BASE_URL: http://127.0.0.1:3003
+ AUTH_SECRET: ci-auth-secret-change-me
+ USE_ANONYMOUS_AUTH_SESSIONS: true
S3_ENDPOINT: http://127.0.0.1:8333
steps:
- uses: actions/checkout@v5
diff --git a/README.md b/README.md
index 44ab3b7..6ded1a6 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ OpenReader is an open source, self-host-friendly text-to-speech document reader
- 🎯 **Multi-provider TTS** — self-hosted OpenAI-compatible servers (Kokoro-FastAPI, KittenTTS-FastAPI, Orpheus-FastAPI) or cloud APIs (OpenAI, Replicate, DeepInfra).
- 🎧 **Audiobook export** in `m4b`/`mp3` with resumable chapter processing.
- 🗂️ **Flexible backend** — embedded SeaweedFS or S3-compatible storage, SQLite or Postgres, server library import, and device sync.
-- 🐳 **Self-host friendly** — Docker (amd64/arm64), optional auth, and automatic startup migrations.
+- 🐳 **Self-host friendly** — Docker (amd64/arm64), built-in auth/session support, and automatic startup migrations.
## 🚀 Start Here
diff --git a/docs-site/docs/configure/admin-panel.md b/docs-site/docs/configure/admin-panel.md
index f8d32df..79029c3 100644
--- a/docs-site/docs/configure/admin-panel.md
+++ b/docs-site/docs/configure/admin-panel.md
@@ -82,9 +82,9 @@ Word-by-word highlighting and PDF layout parsing capability are controlled by co
Each row shows a source badge:
-- **from env** — the value was migrated from the corresponding `RUNTIME_SEED_*` 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.
+- **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.
@@ -105,18 +105,31 @@ The **Disable TTS daily rate limiting** and **Disable PDF parsing rate limiting*
- 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
-The future-direction goal is to remove `RUNTIME_SEED_*` / `API_KEY` / `API_BASE` from your `.env` entirely. To do that safely:
+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:
- - Each `RUNTIME_SEED_*` setting appears as **from env**.
+ - 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 the env vars from your `.env`.
+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 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.
+You can keep `API_BASE` / `API_KEY` if you intentionally want bootstrap fallback behavior on empty provider tables.
## How keys are protected
@@ -133,4 +146,4 @@ Because the encryption key for `admin_providers` is derived from `AUTH_SECRET`,
- [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.
+- [Environment Variables](../reference/environment-variables) — `ADMIN_EMAILS`, provider bootstrap vars, and runtime JSON seed.
diff --git a/docs-site/docs/configure/auth.md b/docs-site/docs/configure/auth.md
index 34e5053..6332449 100644
--- a/docs-site/docs/configure/auth.md
+++ b/docs-site/docs/configure/auth.md
@@ -6,23 +6,21 @@ This page covers application-level configuration for provider access and authent
## Auth behavior
-- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
-- Remove either value to disable auth.
+- `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 effectively has three common runtime modes:
+OpenReader has two 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:
+You can designate one or more users as admins via the `ADMIN_EMAILS` env var:
```env
ADMIN_EMAILS=alice@example.com,bob@example.com
@@ -39,7 +37,7 @@ Admin assignment is reconciled on every session resolution, so removing an email
- `/` is a public landing/onboarding page and remains indexable.
- `/app` is the protected app home (document list and uploader UI).
-- If auth is enabled and a valid session exists (including anonymous), visiting `/` redirects to `/app`.
+- 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
@@ -60,11 +58,7 @@ Admin assignment is reconciled on every session resolution, so removing an email
- 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.
-### Auth disabled
-
-- Settings and reading progress stay local in the browser (Dexie/IndexedDB).
-- This avoids no-auth cross-browser conflicts, but there is no cross-device sync.
-
## 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.
diff --git a/docs-site/docs/configure/database.md b/docs-site/docs/configure/database.md
index d878e9e..b87829a 100644
--- a/docs-site/docs/configure/database.md
+++ b/docs-site/docs/configure/database.md
@@ -54,6 +54,5 @@ For database variable behavior, see [Environment Variables](../reference/environ
## State sync summary
-- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
-- With auth disabled, settings and reading progress remain local in the browser.
+- Settings and reading progress are stored in SQL and synced from the app.
- Sync is currently request-based (not realtime push invalidation).
diff --git a/docs-site/docs/configure/tts-providers.md b/docs-site/docs/configure/tts-providers.md
index 1fa5961..fac1813 100644
--- a/docs-site/docs/configure/tts-providers.md
+++ b/docs-site/docs/configure/tts-providers.md
@@ -11,7 +11,7 @@ OpenReader routes all TTS requests through the Next.js server to an OpenAI-compa
**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 `RUNTIME_SEED_RESTRICT_USER_API_KEYS=false` is still supported for no-admin bootstrap flows.
+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
diff --git a/docs-site/docs/configure/tts-rate-limiting.md b/docs-site/docs/configure/tts-rate-limiting.md
index fedf7f0..967124c 100644
--- a/docs-site/docs/configure/tts-rate-limiting.md
+++ b/docs-site/docs/configure/tts-rate-limiting.md
@@ -8,7 +8,6 @@ This page explains OpenReader's TTS character rate limiting controls.
- TTS rate limiting is disabled by default.
- Primary control is **Settings → Admin → Site features → Disable TTS daily rate limiting**.
-- Optional first-boot seed: `RUNTIME_SEED_DISABLE_TTS_LIMIT=true`.
- Limits are enforced per day in UTC.
- Enforcement applies only when auth is enabled.
@@ -29,10 +28,11 @@ If a request exceeds the active limit, the TTS API returns `429` with reset meta
- `DISABLE_AUTH_RATE_LIMIT` only affects Better Auth's own request throttling.
- `DISABLE_AUTH_RATE_LIMIT` does not disable TTS character limits.
-## Runtime config + seed var
+## Runtime config
-- First-boot seed toggle: `RUNTIME_SEED_DISABLE_TTS_LIMIT` (default: `true`)
+- `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
diff --git a/docs-site/docs/deploy/local-development.md b/docs-site/docs/deploy/local-development.md
index 17830a9..338aeb0 100644
--- a/docs-site/docs/deploy/local-development.md
+++ b/docs-site/docs/deploy/local-development.md
@@ -243,18 +243,7 @@ Use the same ownership split:
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.
-# (Admin panel is unavailable without auth.)
-# API_BASE/API_KEY seed a shared default provider if you want shared mode.
-```
-
-
-
+
```env
API_BASE=http://host.docker.internal:8880/v1
@@ -286,6 +275,8 @@ ADMIN_EMAILS=you@example.com
API_BASE=http://host.docker.internal:8880/v1
API_KEY=none
USE_EMBEDDED_WEED_MINI=false
+BASE_URL=http://localhost:3003
+AUTH_SECRET=
S3_BUCKET=your-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-access-key
@@ -301,6 +292,8 @@ S3_SECRET_ACCESS_KEY=your-secret-key
```env
API_BASE=http://host.docker.internal:8880/v1
API_KEY=none
+BASE_URL=http://localhost:3003
+AUTH_SECRET=
COMPUTE_WORKER_URL=http://localhost:8081
COMPUTE_WORKER_TOKEN=
USE_EMBEDDED_WEED_MINI=false
@@ -317,11 +310,11 @@ S3_SECRET_ACCESS_KEY=your-secret-key
:::note Env vars vs. admin panel
-On first boot, `API_KEY` / `API_BASE` and any `RUNTIME_SEED_*` 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).
+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** when auth/admin is enabled, or via legacy first-boot seed `RUNTIME_SEED_RESTRICT_USER_API_KEYS=false` for no-admin bootstrap flows).
+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
diff --git a/docs-site/docs/deploy/vercel-deployment.md b/docs-site/docs/deploy/vercel-deployment.md
index 86a9b3a..c830ac5 100644
--- a/docs-site/docs/deploy/vercel-deployment.md
+++ b/docs-site/docs/deploy/vercel-deployment.md
@@ -86,14 +86,14 @@ After the first successful deploy and admin login, open **Settings → Admin** a
- `showAllProviderModels=false` if you want users locked to each provider's default model.
- `enableAudiobookExport=true`.
-## 3. Legacy first-boot seed (optional)
+## 3. Runtime JSON seed (optional)
-If you must pre-seed site features via environment variables, the legacy `RUNTIME_SEED_*` seeds are still supported on first boot only. Prefer the admin panel for ongoing management.
+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#legacy-first-boot-runtime-seeds-optional) for the complete legacy seed list.
+See [Environment Variables](../reference/environment-variables#runtime-json-seed-v4) for schema and examples.
:::warning Auth recommendation
-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.
+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
diff --git a/docs-site/docs/docker-quick-start.md b/docs-site/docs/docker-quick-start.md
index f61ea07..d51d351 100644
--- a/docs-site/docs/docker-quick-start.md
+++ b/docs-site/docs/docker-quick-start.md
@@ -33,7 +33,7 @@ OpenReader currently pins embedded SeaweedFS to `4.18` in CI and Docker builds.
-Persistent storage, embedded SeaweedFS `weed mini`, optional auth, optional library mount:
+Persistent storage, embedded SeaweedFS `weed mini`, required auth, optional library mount:
```bash
docker run --name openreader \
@@ -57,7 +57,7 @@ What this command enables:
- `-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=...` / `-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 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.
@@ -95,21 +95,23 @@ What this command enables:
-Auth disabled, embedded storage ephemeral, no library import:
+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:
-- Fastest startup with no extra env vars.
+- 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.
-- Auth remains disabled because `BASE_URL` and `AUTH_SECRET` are not set. The admin panel requires auth, so it's unavailable in this mode.
+- 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.
@@ -117,9 +119,9 @@ What this command enables:
:::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**.
-- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set. The admin panel requires auth.
+- `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 in auth-enabled setups, toggle it off in **Settings → Admin → Site features**. Legacy first-boot seed via `RUNTIME_SEED_RESTRICT_USER_API_KEYS=false` is still supported.
+- `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.
:::
diff --git a/docs-site/docs/introduction.md b/docs-site/docs/introduction.md
index 6d00019..6a43df7 100644
--- a/docs-site/docs/introduction.md
+++ b/docs-site/docs/introduction.md
@@ -23,7 +23,7 @@ It supports multiple TTS providers including OpenAI, Replicate, DeepInfra, and c
- 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 Optional** — run no-auth for local use, or enable full user isolation with a claim flow for multi-user deployments
+- 🔐 **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
diff --git a/docs-site/docs/reference/environment-variables.md b/docs-site/docs/reference/environment-variables.md
index 2fc71f9..2389c18 100644
--- a/docs-site/docs/reference/environment-variables.md
+++ b/docs-site/docs/reference/environment-variables.md
@@ -3,36 +3,29 @@ title: Environment Variables
toc_max_heading_level: 3
---
-This is the single reference page for OpenReader environment variables.
+This page is the source-of-truth reference 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 `RUNTIME_SEED_*`) are optional first-boot seeds only.
+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; shared by app server + compute worker |
+| `LOG_FORMAT` | Runtime logging | `pretty` | Set `json` for structured logs |
| `LOG_LEVEL` | Runtime logging | `info` | Set app server log level |
-| `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 |
-| `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 |
-| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
-| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
-| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
-| `TTS_UPSTREAM_TIMEOUT_MS` | TTS request timeout | `285000` | Set max upstream TTS request duration before fail-fast |
-| `BASE_URL` | Auth | unset | Required (with `AUTH_SECRET`) to enable auth |
-| `AUTH_SECRET` | Auth | unset | Required (with `BASE_URL`) to enable auth |
+| `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 enable anonymous auth sessions |
+| `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 |
-| `DISABLE_AUTH_RATE_LIMIT` | Rate limiting | `false` | Set `true` to disable auth-layer rate limiting |
-| `ENABLE_TEST_NAMESPACE` | Testing/CI | unset | Honor the `x-openreader-test-namespace` header on a production build; leave unset on real deployments |
-| `ADMIN_EMAILS` | Auth/Admin | empty | Comma-separated emails auto-promoted to admin (requires auth enabled) |
+| `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 |
@@ -46,36 +39,27 @@ For auth-enabled deployments, use **Settings → Admin** as the primary source o
| `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) |
-| `COMPUTE_WORKER_URL` | Heavy compute backend | unset | Set only for standalone external compute worker; leave unset for embedded worker startup |
-| `COMPUTE_WORKER_TOKEN` | Heavy compute backend | unset (auto-generated in embedded startup) | Required for standalone external compute worker auth; must match worker |
-| `EMBEDDED_COMPUTE_WORKER_PORT` | Heavy compute backend | `8081` | Override embedded worker bind port |
-| `EMBEDDED_NATS_PORT` | Heavy compute backend | `4222` | Override embedded NATS client port |
-| `EMBEDDED_NATS_MONITOR_PORT` | Heavy compute backend | `8222` | Override embedded NATS monitor port |
-| `EMBEDDED_NATS_STORE_DIR` | Heavy compute backend | `docstore/nats/jetstream` | Override embedded JetStream storage directory |
-| `NATS_URL` | Heavy compute backend | `nats://127.0.0.1:4222` in embedded startup | Optional override for embedded startup or required on standalone worker service |
-| `COMPUTE_LOG_LEVEL` | Heavy compute backend | `info` | Compute worker log level |
-| `COMPUTE_JOB_CONCURRENCY` | Heavy compute backend | `1` | Worker-side shared compute concurrency cap |
-| `COMPUTE_WHISPER_TIMEOUT_MS` | Heavy compute backend | `30000` | Shared whisper alignment timeout budget (worker + worker client wait budget) |
-| `COMPUTE_PDF_TIMEOUT_MS` | Heavy compute backend | `300000` | Shared PDF idle-timeout budget (worker + worker client wait budget) |
-| `COMPUTE_OP_STALE_MS` | Heavy compute backend | `max(30m, 4x max compute timeout)` | Shared stale window for worker op replacement and app-side stale PDF parse-state healing |
-| `PDF_LAYOUT_MODEL_BASE_URL` | PDF layout model | PP-DocLayoutV3 ONNX base URL | Optional base URL override for `ensureModel()` |
-| `WHISPER_MODEL_BASE_URL` | Whisper ONNX model | onnx-community defaults | Optional base URL override for ONNX whisper-base_timestamped q4 downloads |
+| `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 |
-| `RUNTIME_SEED_*` runtime seeds | Legacy bootstrap seed | varies | Optional first-boot seeds for site features; then manage in Settings → Admin → Site features |
-| `RUNTIME_SEED_ENABLE_DOCX_CONVERSION` | Legacy bootstrap seed | `true` | Optional first-boot seed to enable/disable DOCX conversion UI |
-| `RUNTIME_SEED_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Legacy bootstrap seed | `true` | Optional first-boot seed to show/hide destructive delete actions |
-| `RUNTIME_SEED_ENABLE_TTS_PROVIDERS_TAB` | Legacy bootstrap seed | `true` | Optional first-boot seed to show/hide user TTS providers tab |
-| `RUNTIME_SEED_CHANGELOG_FEED_URL` | Legacy bootstrap seed | `https://docs.openreader.richardr.dev/changelog/manifest.json` | Optional first-boot seed for changelog feed URL; then manage in Settings → Admin → Site features |
-| `RUNTIME_SEED_ENABLE_USER_SIGNUPS` | Legacy bootstrap seed | `true` | Optional first-boot seed for whether new accounts can be created; then manage in Settings → Admin → Site features |
-| `RUNTIME_SEED_RESTRICT_USER_API_KEYS` | Legacy bootstrap seed | runtime-dependent | Optional first-boot seed to restrict per-user BYOK |
-| `RUNTIME_SEED_DEFAULT_TTS_PROVIDER` | Legacy bootstrap seed | `custom-openai` | Optional first-boot seed for default TTS provider slug |
-| `RUNTIME_SEED_ENABLE_AUDIOBOOK_EXPORT` | Legacy bootstrap seed | `true` | Optional first-boot seed to enable audiobook export UI |
-| `RUNTIME_SEED_DISABLE_TTS_LIMIT` | Legacy bootstrap seed | `true` | Optional first-boot seed that keeps TTS daily rate limiting disabled |
-| `RUNTIME_SEED_DISABLE_COMPUTE_LIMIT` | Legacy bootstrap seed | `true` | Optional first-boot seed that keeps PDF parsing rate limiting disabled (other compute-limit values + max upload size are admin-only) |
-| `RUN_DRIZZLE_MIGRATIONS` | Database migrations | `true` | Set `false` to skip startup Drizzle schema migrations |
+| `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
@@ -86,7 +70,6 @@ Controls log output format for server-side Pino loggers.
- Default: `pretty`
- Allowed values: `pretty`, `json`
- Applies to app server and compute worker
-- Recommended in production (Vercel + external worker): `json`
### LOG_LEVEL
@@ -98,157 +81,88 @@ App server log level.
### API_BASE
-Bootstrap base URL for the legacy OpenAI-compatible TTS endpoint.
+Optional first-boot bootstrap base URL for the auto-created `default-openai` shared provider.
- Example: `http://host.docker.internal:8880/v1`
-- **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)
+- 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
-Bootstrap API key for the legacy OpenAI-compatible TTS endpoint.
+Optional first-boot bootstrap API key for the auto-created `default-openai` shared provider.
-- Example: your provider token, or omit if the provider doesn't require auth
-- **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
-
-Maximum in-memory TTS audio cache size in bytes.
-
-- Default: `268435456` (256 MB)
-
-### TTS_CACHE_TTL_MS
-
-In-memory TTS audio cache TTL in milliseconds.
-
-- Default: `1800000` (30 minutes)
-
-### TTS_MAX_RETRIES
-
-Maximum retries for upstream TTS failures (429/5xx).
-
-- Default: `2`
-
-### TTS_RETRY_INITIAL_MS
-
-Initial retry delay in milliseconds for TTS upstream requests.
-
-- Default: `250`
-
-### TTS_RETRY_MAX_MS
-
-Maximum retry delay in milliseconds.
-
-- Default: `2000`
-
-### TTS_RETRY_BACKOFF
-
-Exponential backoff multiplier between retries.
-
-- Default: `2`
-
-### TTS_UPSTREAM_TIMEOUT_MS
-
-Maximum upstream TTS request timeout in milliseconds.
-
-- Default: `285000` (285 seconds)
-- Applies to outbound provider calls from server routes using shared TTS generation
-- Increase for slower providers/models; decrease to fail fast and surface retryable errors sooner
+- 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)
-TTS character rate limiting is now managed from **Settings → Admin → Site features**.
+Managed as runtime config in **Settings → Admin → Site features**.
-- `disableTtsRateLimit` default: `true` (rate limiting disabled)
-- Daily limit defaults:
- - Anonymous per-user: `50000`
- - Authenticated per-user: `500000`
- - Anonymous IP backstop: `100000`
- - Authenticated IP backstop: `1000000`
+- `disableTtsRateLimit` default: `true` (daily TTS limits disabled)
+- `ttsDailyLimitAnonymous` default: `50000`
+- `ttsDailyLimitAuthenticated` default: `500000`
+- `ttsIpDailyLimitAnonymous` default: `100000`
+- `ttsIpDailyLimitAuthenticated` default: `1000000`
-Optional first-boot seeds:
+### TTS Upstream Settings (Runtime Settings)
-- `RUNTIME_SEED_DISABLE_TTS_LIMIT`
+Managed as runtime config in **Settings → Admin → Site features → TTS upstream**.
-After first boot, these values are DB-backed admin runtime settings.
+- `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
-External base URL for this OpenReader instance.
+Required external base URL for this OpenReader instance.
-- Required with `AUTH_SECRET` to enable auth
+- Required at startup
- Example: `http://localhost:3003` or `https://reader.example.com`
-- Related docs: [Auth](../configure/auth)
### AUTH_SECRET
-Secret key used by auth/session handling.
+Required secret key used by auth/session handling.
-- Required with `BASE_URL` to enable auth
+- Required at startup
- Generate with `openssl rand -hex 32`
-- Also used to HMAC-hash server-side TTS segment text fingerprints
-- Related docs: [Auth](../configure/auth)
### AUTH_TRUSTED_ORIGINS
Additional allowed origins for auth requests.
- Comma-separated list
-- `BASE_URL` origin is always trusted automatically
-- Related docs: [Auth](../configure/auth)
+- `BASE_URL` origin is trusted automatically
### USE_ANONYMOUS_AUTH_SESSIONS
Controls whether auth-enabled deployments can create/use anonymous sessions.
-- Default: `false` (anonymous sessions disabled)
-- Set `true` to allow anonymous sessions and guest-style flows
-- When `false`, users must sign in or sign up with an account
-- Related docs: [Auth](../configure/auth)
+- Default: `false`
### GITHUB_CLIENT_ID
GitHub OAuth client ID.
-- Enable only with `GITHUB_CLIENT_SECRET`
+- Set with `GITHUB_CLIENT_SECRET`
### GITHUB_CLIENT_SECRET
GitHub OAuth client secret.
-- Enable only with `GITHUB_CLIENT_ID`
-
-### DISABLE_AUTH_RATE_LIMIT
-
-Controls Better Auth rate limiting.
-
-- Default behavior: auth-layer rate limiting enabled
-- Set to `true` to disable auth-layer rate limiting
-- This does not affect TTS character rate limiting
-- Related docs: [Auth](../configure/auth)
-
-### ENABLE_TEST_NAMESPACE
-
-Honors the `x-openreader-test-namespace` request header, which scopes documents/storage into an isolated namespace for end-to-end tests.
-
-- Default: unset (header ignored on production builds)
-- Non-production builds (`NODE_ENV !== 'production'`) honor the header without this flag.
-- Production builds (`pnpm build && pnpm start`) honor it only when `ENABLE_TEST_NAMESPACE=true`. The Playwright web server sets this automatically.
-- **Leave unset on real deployments.** It is test/CI scaffolding only.
+- Set with `GITHUB_CLIENT_ID`
### ADMIN_EMAILS
-Comma-separated list of email addresses that are auto-promoted to admin.
+Comma-separated list of email addresses 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)
+- Requires auth to be enabled
+- Admins can manage shared providers and runtime site features in-app
## Database and Object Blob Storage
@@ -258,9 +172,6 @@ Switches metadata/auth storage from SQLite to Postgres.
- Unset: SQLite at `docstore/sqlite3.db`
- Set: Postgres mode
-- Related docs: [Database](../configure/database)
-
-### Embedded SeaweedFS weed mini config
### USE_EMBEDDED_WEED_MINI
@@ -268,129 +179,83 @@ Controls embedded SeaweedFS startup.
- Default behavior: treated as enabled when unset
- Set `false` to rely on external S3-compatible storage
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
### WEED_MINI_DIR
Data directory for embedded SeaweedFS (`weed mini`).
- Default: `docstore/seaweedfs`
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
### WEED_MINI_WAIT_SEC
-Maximum seconds to wait for embedded SeaweedFS startup.
+Max wait time for embedded SeaweedFS startup.
- Default: `20`
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
-
-### S3 storage config
### S3_ACCESS_KEY_ID
-Access key for S3-compatible storage.
+S3 access key.
-- Auto-generated in embedded mode if unset
-- Set explicitly for stable credentials or external providers
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+- Optional in embedded mode (auto-generated when unset)
+- Required for external S3 mode
### S3_SECRET_ACCESS_KEY
-Secret key for S3-compatible storage.
+S3 secret key.
-- Auto-generated in embedded mode if unset
-- Set explicitly for stable credentials or external providers
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+- Optional in embedded mode (auto-generated when unset)
+- Required for external S3 mode
### S3_BUCKET
-Bucket name used for document blobs.
+S3 bucket name.
-- Default in embedded mode: `openreader-documents`
-- Required for external S3-compatible storage
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+- Embedded default: `openreader-documents`
+- Required for external S3 mode
### S3_REGION
-Region used by the S3 client.
+S3 region.
-- Default in embedded mode: `us-east-1`
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+- Embedded default: `us-east-1`
+- Required for external S3 mode
### S3_ENDPOINT
-Endpoint URL for S3-compatible storage.
+Custom endpoint for S3-compatible providers.
-- In embedded mode, defaults to `http://:8333` (or detected host)
-- For AWS S3, usually leave unset
-- For MinIO/SeaweedFS/R2/B2-style APIs, typically set explicitly
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+- Optional for AWS
+- Typical for MinIO/SeaweedFS/R2
### S3_FORCE_PATH_STYLE
-Path-style S3 addressing toggle.
+Force path-style S3 URLs.
-- Default in embedded mode: `true`
-- Set according to provider requirements
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+- Embedded default: `true`
### S3_PREFIX
-Prefix prepended to stored object keys.
+Object key prefix.
- Default: `openreader`
-- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
-
-### Maximum Upload Size (Runtime Settings)
-
-The maximum size accepted for a single document upload is an admin runtime setting (no env var).
-
-- Runtime key: `maxUploadMb` (default: `200`)
-- Configure in **Settings → Admin → Site features → Max upload size (MB)**.
-- Enforced up front (`413`) and signed into the presigned S3 PUT so a client cannot stream more than it declared.
## Library Import
### IMPORT_LIBRARY_DIR
-Single directory root for server library import.
-
-- Used when `IMPORT_LIBRARY_DIRS` is unset
-- Default fallback root: `docstore/library`
-- Related docs: [Server Library Import](../configure/server-library-import)
+Single library source directory.
### IMPORT_LIBRARY_DIRS
-Multiple library roots for server library import.
+Multiple library roots.
-- Separator: comma, colon, or semicolon
-- Takes precedence over `IMPORT_LIBRARY_DIR`
-- Related docs: [Server Library Import](../configure/server-library-import)
+- Supports comma, colon, or semicolon-separated values
-## Audio Tooling and Alignment
-
-### COMPUTE_WORKER_URL
-
-Base URL for standalone external compute worker mode.
-
-- Leave unset for embedded/local startup (`pnpm dev` / `pnpm start`) so entrypoint can start embedded worker+NATS.
-- Embedded startup requires `nats-server` available on host PATH.
-- Required only when using a standalone external worker service.
-- App-side only: set on app server/root `.env` (routing target), not worker-only env files.
-- Example: `http://localhost:8081`
-
-### COMPUTE_WORKER_TOKEN
-
-Bearer token for compute-worker auth.
-
-- Required for standalone external worker service mode.
-- Must match worker service `COMPUTE_WORKER_TOKEN`.
-- In embedded startup, entrypoint auto-generates one if unset.
-- In external worker mode, set this on both app server/root `.env` and worker service env (`compute/worker/.env*` or platform env).
+## Compute Worker and Model Configuration
### EMBEDDED_COMPUTE_WORKER_PORT
-Embedded compute-worker HTTP port.
+Embedded compute worker port.
- Default: `8081`
@@ -402,213 +267,200 @@ Embedded NATS client port.
### EMBEDDED_NATS_MONITOR_PORT
-Embedded NATS monitor (`/healthz`) port.
+Embedded NATS monitor port.
- Default: `8222`
### EMBEDDED_NATS_STORE_DIR
-Embedded JetStream storage directory.
+Embedded NATS JetStream data directory.
- Default: `docstore/nats/jetstream`
### NATS_URL
-NATS connection URL used by compute worker runtime.
+NATS URL used by compute services.
- Embedded startup default: `nats://127.0.0.1:4222`
-- Standalone worker service: set in worker service env (`compute/worker/.env*` or platform env)
-- For embedded startup, this is optional; startup supplies the default value.
-- Worker-side only in external mode: set on worker service env, not app/root `.env`.
### COMPUTE_LOG_LEVEL
Compute worker log level.
- Default: `info`
-- In standalone mode, set this on the worker service env.
### COMPUTE_JOB_CONCURRENCY
-Worker-side shared compute concurrency cap.
+Max concurrent compute jobs per worker.
- Default: `1`
-- Set on the compute-worker service environment
### COMPUTE_WHISPER_TIMEOUT_MS
-Shared whisper alignment timeout budget in milliseconds.
+Whisper alignment timeout budget.
- Default: `30000`
-- Used by:
- - Worker compute whisper runtime
- - App server worker-client wait budget (SSE wait timeout)
### COMPUTE_PDF_TIMEOUT_MS
-Shared PDF idle-timeout budget in milliseconds.
+PDF parse timeout budget.
-- Default: `300000` (5 minutes)
-- Used by:
- - Worker compute PDF runtime (idle timeout)
- - App server worker-client wait budget (SSE wait timeout)
+- Default: `300000`
### COMPUTE_OP_STALE_MS
-Shared stale window in milliseconds.
+Stale operation window before worker/app cleanup logic can replace an op.
-- Default: `max(30m, 4x max(COMPUTE_WHISPER_TIMEOUT_MS, COMPUTE_PDF_TIMEOUT_MS))`
-- Used by:
- - Worker op reuse/replacement guard (`/ops` opKey stale detection)
- - App-server PDF parse-state stale healing in `/api/documents/[id]/parsed*`
-- If a parse row is stuck in `pending`/`running` past this window, app routes mark it failed so retries/reparse can proceed.
-- Keep this value aligned on both app-server and worker service envs.
-
-### PDF_LAYOUT_MODEL_BASE_URL
-
-Optional base URL override for PP-DocLayoutV3 artifacts downloaded by `ensureModel()`.
-
-- Default: `https://huggingface.co/Bei0001/PP-DocLayoutV3-ONNX/resolve/main`
-- Required files at that base:
- - `PP-DocLayoutV3.onnx`
- - `PP-DocLayoutV3.onnx.data`
- - `config.json`
- - `preprocessor_config.json`
-- Configure this on the worker service env (not only the app server env)
+- Default: `max(30m, 4x max compute timeout)`
### WHISPER_MODEL_BASE_URL
-Optional base URL override for the built-in ONNX Whisper alignment model downloader.
+Base URL for Whisper ONNX model downloads.
-- Default: `https://huggingface.co/onnx-community/whisper-base_timestamped/resolve/main`
-- Default model variant: q4 (`encoder_model_q4.onnx`, `decoder_model_merged_q4.onnx`, `decoder_with_past_model_q4.onnx`)
-- The base URL must host all expected manifest files under the same relative paths.
-- Configure this on the worker service env (not only the app server env)
+### 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
-Absolute path or executable name for the ffmpeg binary used by audiobook/processing routes.
+Override ffmpeg binary path used for audio processing.
-- Resolution order: `FFMPEG_BIN` -> `ffmpeg-static`
-- Example: `/var/task/node_modules/ffmpeg-static/ffmpeg`
+- Used by audiobook processing routes and compute worker Whisper audio decode.
-### Compute (PDF Parsing) Rate Limiting (Runtime Settings)
+## Testing and CI
-Per-user throttling of expensive PDF layout parsing is managed from **Settings → Admin → Site features**, not env vars. Enforcement applies only when auth is enabled.
+### DISABLE_AUTH_RATE_LIMIT
-- `disableComputeRateLimit` default: `true` (rate limiting disabled, like the TTS limit; enable it in the admin panel)
-- When enabled, the following admin-tunable sub-limits apply:
- - `computeParseBurstMax` (default `8`) over `computeParseBurstWindowSec` (default `60`)
- - `computeParseSustainedMax` (default `24`) over `computeParseSustainedWindowSec` (default `600`)
-- The sustained window also acts as a concurrency cap: because the worker bounds each job's duration, the count of parses started in that window is an upper bound on those still running.
-- Exceeding a limit returns `429` (`application/problem+json`) with `Retry-After`.
+Disables Better Auth request rate limiting.
-Optional first-boot seed: `RUNTIME_SEED_DISABLE_COMPUTE_LIMIT`. All other values are DB-backed admin runtime settings.
+- Default: `false`
-## Legacy First-Boot Runtime Seeds (optional)
+### ENABLE_TEST_NAMESPACE
-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.__RUNTIME_CONFIG__`, so admin edits take effect for all users on the next page load — no rebuild required (unlike the old build-time public env pattern).
-
-### RUNTIME_SEED_ENABLE_DOCX_CONVERSION
-
-Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
-
-- Default: `true` (enabled)
-- Runtime key: `enableDocxConversion`
-
-### RUNTIME_SEED_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`
-
-### RUNTIME_SEED_ENABLE_TTS_PROVIDERS_TAB
-
-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 the per-user Settings modal (the admin panel is unaffected).
-- Runtime key: `enableTtsProvidersTab`
-
-### RUNTIME_SEED_ENABLE_USER_SIGNUPS
-
-Controls whether new user accounts can be created.
-
-- Default: `true` (enabled)
-- When `false`, new account creation is blocked for email sign-up, first-time OAuth signup, and anonymous-to-account upgrades.
-- Existing users can still sign in.
-- Runtime key: `enableUserSignups`
-
-### RUNTIME_SEED_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`
-
-### RUNTIME_SEED_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`
-
-`showAllProviderModels` is a runtime-only admin setting (no env seed). Configure it in **Settings → Admin → Site features**.
-
-### RUNTIME_SEED_CHANGELOG_FEED_URL
-
-Sets the changelog manifest URL used by the Settings modal changelog viewer.
-
-- Default: `https://docs.openreader.richardr.dev/changelog/manifest.json`
-- Use this in self-hosted deployments when you publish changelog feeds to a custom docs domain/path.
-- Runtime key: `changelogFeedUrl`
-
-
-### RUNTIME_SEED_ENABLE_AUDIOBOOK_EXPORT
-
-Controls whether audiobook export UI/actions are shown in the client.
-
-- Default: `true` (enabled)
-- Affects export entry points in PDF/EPUB pages and document settings UI
-- Runtime key: `enableAudiobookExport`
-
-### RUNTIME_SEED_DISABLE_TTS_LIMIT
-
-Seeds the TTS daily character rate-limit on/off state on first boot.
-
-- Default: `true` (TTS rate limiting disabled)
-- Runtime key: `disableTtsRateLimit`
-- Per-user/IP daily limit values are admin-only runtime settings (see [TTS Daily Rate Limiting](#tts-daily-rate-limiting-runtime-settings)).
-
-### RUNTIME_SEED_DISABLE_COMPUTE_LIMIT
-
-Seeds the PDF parsing rate-limit on/off state on first boot.
-
-- Default: `true` (PDF parsing rate limiting disabled, matching `RUNTIME_SEED_DISABLE_TTS_LIMIT`)
-- Runtime key: `disableComputeRateLimit`
-- The burst/sustained limits, their windows, and the max upload size are admin-only runtime settings (see [Compute (PDF Parsing) Rate Limiting](#compute-pdf-parsing-rate-limiting-runtime-settings)).
+Enables the `x-openreader-test-namespace` header path in production builds.
## Migration Controls
### RUN_DRIZZLE_MIGRATIONS
-Controls startup migration execution in shared entrypoint.
+Controls startup Drizzle schema migrations.
- Default: `true`
-- Set `false` to skip automatic startup Drizzle schema migrations
-- Related docs: [Migrations](../configure/migrations), [Database](../configure/database)
+- Set `false` to skip startup migration run
### RUN_FS_MIGRATIONS
-Controls startup filesystem-to-object-store migration execution in shared entrypoint.
+Controls startup filesystem-to-S3/DB migration pass.
- Default: `true`
-- Runs `scripts/migrate-fs-v2.mjs` at startup after DB migrations
-- Set `false` to skip automatic storage migration pass
-- Related docs: [Migrations](../configure/migrations), [Database](../configure/database), [Object / Blob Storage](../configure/object-blob-storage)
+- 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)
diff --git a/package.json b/package.json
index a7aa4d5..e208eda 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,6 @@
"test:e2e": "playwright test",
"test:unit": "vitest run",
"test:compute": "vitest run --project compute-core --project compute-worker",
- "test:ci-env": "CI=true playwright test",
"migrate": "node drizzle/scripts/migrate.mjs",
"migrate-fs": "node scripts/migrate-fs-v2.mjs",
"migrate-fs:dry-run": "node scripts/migrate-fs-v2.mjs --dry-run true",
diff --git a/playwright.config.ts b/playwright.config.ts
index 07b27bb..d38c61e 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -1,15 +1,5 @@
import { defineConfig, devices } from '@playwright/test';
import 'dotenv/config';
-import fs from 'node:fs';
-import path from 'node:path';
-import dotenv from 'dotenv';
-
-if (process.env.CI === 'true') {
- const envCiPath = path.join(process.cwd(), '.env.ci');
- if (fs.existsSync(envCiPath)) {
- dotenv.config({ path: envCiPath, override: true });
- }
-}
/**
* See https://playwright.dev/docs/test-configuration.
diff --git a/scripts/openreader-entrypoint.mjs b/scripts/openreader-entrypoint.mjs
index 06dd1d4..2fa3e86 100644
--- a/scripts/openreader-entrypoint.mjs
+++ b/scripts/openreader-entrypoint.mjs
@@ -11,15 +11,6 @@ import * as dotenv from 'dotenv';
function loadEnvFiles() {
const cwd = process.cwd();
- const isCi = isTrue(process.env.CI, false);
- if (isCi) {
- const envCiPath = path.join(cwd, '.env.ci');
- if (fs.existsSync(envCiPath)) {
- dotenv.config({ path: envCiPath });
- }
- return;
- }
-
const envPath = path.join(cwd, '.env');
const envLocalPath = path.join(cwd, '.env.local');
@@ -46,6 +37,18 @@ function withDefault(value, fallback) {
return value && value.trim() ? value.trim() : fallback;
}
+function requireAuthEnv(env) {
+ const missing = [];
+ if (!env.AUTH_SECRET?.trim()) missing.push('AUTH_SECRET');
+ if (!env.BASE_URL?.trim()) missing.push('BASE_URL');
+ if (missing.length > 0) {
+ throw new Error(
+ `Missing required auth env vars: ${missing.join(', ')}. `
+ + 'OpenReader v4 requires both AUTH_SECRET and BASE_URL at startup.',
+ );
+ }
+}
+
function isPrivateIPv4(address) {
if (!address) return false;
if (address.startsWith('10.')) return true;
@@ -328,6 +331,7 @@ async function main() {
const runtimeEnv = { ...process.env };
runtimeEnv.LOG_FORMAT = withDefault(runtimeEnv.LOG_FORMAT, 'pretty');
+ requireAuthEnv(runtimeEnv);
let weedProc = null;
let weedExitPromise = Promise.resolve();
let natsProc = null;
diff --git a/src/app/(app)/epub/[id]/useEpubDocument.ts b/src/app/(app)/epub/[id]/useEpubDocument.ts
index 8c5a78a..6dc2b65 100644
--- a/src/app/(app)/epub/[id]/useEpubDocument.ts
+++ b/src/app/(app)/epub/[id]/useEpubDocument.ts
@@ -18,7 +18,6 @@ import { EpubRenderedLocationCloneManager } from '@/lib/client/epub/rendered-loc
import { canonicalizeEpubSegmentAgainstSpineText } from '@/lib/client/epub/canonicalize-epub-segment';
import { buildEpubLocator, getSpineItemPlainText } from '@/lib/client/epub/spine-coordinates';
import { useTTS, type EpubLocatorResolver } from '@/contexts/TTSContext';
-import { useAuthConfig } from '@/contexts/AuthRateLimitContext';
import { createRangeCfi } from '@/lib/client/epub';
import { normalizeTtsLocationKey } from '@/lib/shared/tts-locator';
import { useConfig } from '@/contexts/ConfigContext';
@@ -94,7 +93,6 @@ export interface EpubDocumentState {
*/
export function useEpubDocument(documentId?: string): EpubDocumentState {
const { setText: setTTSText, currDocPage, currDocPages, setCurrDocPages, stop, skipToLocation, setIsEPUB } = useTTS();
- const { authEnabled } = useAuthConfig();
// Configuration context to get TTS settings
const {
apiKey,
@@ -372,7 +370,6 @@ export function useEpubDocument(documentId?: string): EpubDocumentState {
const handleLocationChanged = useEPUBLocationController({
documentId,
- authEnabled,
isEpubSetOnceRef: isEPUBSetOnce,
shouldPauseRef,
setIsEpub: setIsEPUB,
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
index c554046..dcf5127 100644
--- a/src/app/(app)/layout.tsx
+++ b/src/app/(app)/layout.tsx
@@ -3,7 +3,7 @@ import type { ReactNode } from 'react';
import { Toaster } from 'react-hot-toast';
import { Providers } from '@/app/providers';
-import { getAuthBaseUrl, isAnonymousAuthSessionsEnabled, isAuthEnabled, isGithubAuthEnabled } from '@/lib/server/auth/config';
+import { getAuthBaseUrl, isAnonymousAuthSessionsEnabled, isGithubAuthEnabled } from '@/lib/server/auth/config';
export const dynamic = 'force-dynamic';
@@ -22,14 +22,12 @@ export const metadata: Metadata = {
};
export default function AppLayout({ children }: { children: ReactNode }) {
- const authEnabled = isAuthEnabled();
const authBaseUrl = getAuthBaseUrl();
const allowAnonymousAuthSessions = isAnonymousAuthSessionsEnabled();
const githubAuthEnabled = isGithubAuthEnabled();
return (
(null);
- const { authEnabled, baseUrl, allowAnonymousAuthSessions, githubAuthEnabled } = useAuthConfig();
+ const { baseUrl, allowAnonymousAuthSessions, githubAuthEnabled } = useAuthConfig();
const enableUserSignups = useFeatureFlag('enableUserSignups');
const { refresh: refreshRateLimit } = useAuthRateLimit();
const isAnyLoading = loadingEmail || loadingGithub || loadingAnonymous;
- // Check if auth is enabled, redirect home if not
- useEffect(() => {
- if (!authEnabled) {
- router.push('/app');
- }
- }, [router, authEnabled]);
-
const validateEmail = (email: string): boolean => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
@@ -121,10 +114,6 @@ function SignInContent() {
}
};
- if (!authEnabled) {
- return null;
- }
-
return (