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