docs(admin-panel): clarify admin provider key handling and AUTH_SECRET rotation

Expand admin panel documentation to detail the transition from live env var
reading to one-shot seeding for TTS providers in v3.0.0. Add warnings about
the impact of rotating AUTH_SECRET on encrypted admin provider keys and the
required manual steps after rotation.

docs(migrations): add schema migration history table

Document the sequence and purpose of each migration introduced in v3.0.0,
clarifying upgrade behavior from v2.2.0 and summarizing schema changes.

chore(env): remove commented default TTS model example from .env.example
This commit is contained in:
Richard R 2026-05-14 13:56:53 -06:00
parent ca4228970b
commit 80f9386a6c
3 changed files with 20 additions and 6 deletions

View file

@ -94,6 +94,5 @@ FFMPEG_BIN=
# NEXT_PUBLIC_RESTRICT_USER_API_KEYS=true # NEXT_PUBLIC_RESTRICT_USER_API_KEYS=true
# NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=custom-openai # NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=custom-openai
# NEXT_PUBLIC_CHANGELOG_FEED_URL=https://docs.openreader.richardr.dev/changelog/manifest.json # NEXT_PUBLIC_CHANGELOG_FEED_URL=https://docs.openreader.richardr.dev/changelog/manifest.json
# NEXT_PUBLIC_DEFAULT_TTS_MODEL=kokoro
# NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true # NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
# NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=true # NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=true

View file

@ -58,6 +58,10 @@ On first boot, if `admin_providers` is empty and the legacy `API_KEY` env var is
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. 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 ## Site features
Runtime-editable settings, one row per key: Runtime-editable settings, one row per key:
@ -100,11 +104,15 @@ You can keep the env vars indefinitely if you prefer; they're only read on the f
## How keys are protected ## How keys are protected
- API keys are encrypted in the `admin_providers` table with AES-256-GCM. The encryption key is derived from `AUTH_SECRET` via `scrypt`. This means rotating `AUTH_SECRET` will invalidate all stored admin keys — re-enter them via the admin UI after rotating. - 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 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. - 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. - 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 ## Related
- [Auth](./auth) — required to use the admin panel. - [Auth](./auth) — required to use the admin panel.

View file

@ -20,14 +20,21 @@ Startup migration phases:
- DB schema migrations (`pnpm migrate`) - DB schema migrations (`pnpm migrate`)
- Storage/data migration (`pnpm migrate-fs`) for legacy filesystem content into S3 + DB rows - Storage/data migration (`pnpm migrate-fs`) for legacy filesystem content into S3 + DB rows
Recent schema addition:
- `0001_tts_segments` (SQLite + Postgres) creates the `tts_segments` table used by server-side TTS segment caching and alignment metadata.
:::info :::info
In most setups, you do not need to run migration commands manually because startup handles this automatically. 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: To skip automatic startup migrations:
- Set `RUN_DRIZZLE_MIGRATIONS=false` - Set `RUN_DRIZZLE_MIGRATIONS=false`