pediatric-ai-scribe-v3/docs/configuration.md
Daniel ceccd18387
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Docker Build / Build Docker image (push) Successful in 22s
Forgejo Docker Build / Deploy to the host (push) Failing after 1s
docs: take the Learning Hub out of the docs
The feature was removed; the docs still described it as live. Worst of
it was api-reference.md, which documented nine /api/learning endpoints
and fourteen /api/admin/learning CMS endpoints — routes that answer 404
— plus POST /api/user/webdav-path, whose column was dropped by
migration. Anyone reading them was reading fiction.

Checked against the running system rather than assumed: no learning
table exists, users.webdav_learning_path is gone, generated_image_links
is gone, and no route mounts /api/learning or /api/admin/learning.

Two things that look like Learning Hub and are not, so they stay:

- learningRetrieval.js is live — My Resources uses it. Its settings keep
  the learning.* names because renaming them would orphan whatever an
  administrator has already set. retrieval-tuning.md now says so instead
  of listing the rows under two different feature names.
- the moderator role is still assignable. It gated the CMS and now
  grants nothing; authentication.md says that rather than implying
  powers it does not have. moderatorMiddleware has no callers left,
  which is worth removing on its own.

auth-admin-learning.md is now auth-admin.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 22:24:29 +02:00

218 lines
11 KiB
Markdown

# Configuration
Runtime configuration sources, in override order (later wins for overlapping
keys):
1. `.env` file / container environment variables (startup only)
2. `app_settings` table (live, editable from Admin Panel with 2-minute cache)
## Environment variables
### Core (production-required)
| Variable | Purpose |
|---|---|
| `APP_URL` | Public base URL. Enables production mode — fail-closed CORS, HSTS, secure cookies. |
| `JWT_SECRET` | HMAC key for JWT signing and OIDC state. Server refuses to start without it in production. |
| `DATA_ENCRYPTION_KEY` | AES-256-GCM key for PHI at rest (Nextcloud tokens, audio backups). 64 hex chars (`openssl rand -hex 32`). Refuses to start without it in production. |
| `AUDIO_BACKUPS_S3_ENDPOINT`, `_BUCKET`, `_REGION` | Optional. Keeps the 24-hour recording copies in object storage instead of the `audio_backups` column; metadata stays in Postgres either way. See `docs/speech.md`. |
| `AUDIO_BACKUPS_S3_ACCESS_KEY_FILE`, `_SECRET_KEY_FILE` | Credentials for the above, read from files so they never appear in the process environment. `AUDIO_BACKUPS_S3_ACCESS_KEY` / `_SECRET_KEY` are the inline fallback. |
| `DB_PASSWORD` / `DATABASE_URL` | Postgres password or full connection string. |
| `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_NAME` | Read by `scripts/maintenance.js` when `DATABASE_URL` is not set (defaults `postgres` / `5432` / `pedscribe` / `pedscribe`). The app itself takes its connection from `DATABASE_URL` / `DB_PASSWORD`. |
| `REDIS_URL` | Redis for operational cache and prompt suggestions. Absent means those features degrade, not that the app fails. |
| `SITE_NAME` | Name used in emails and page titles (default `Pediatric AI Scribe`). |
| `PORT` | HTTP listen port (default 3000). |
| `NODE_ENV` | `production` forces prod-only guards on even without `APP_URL`. |
### CORS
| Variable | Purpose |
|---|---|
| `CORS_ORIGINS` | Comma-separated additional allowed origins beyond `APP_URL`. |
### AI provider
| Variable | Purpose |
|---|---|
| `AI_PROVIDER` | `litellm` / `openrouter` / `bedrock` / `azure`. If unset, the default is `litellm` when `LITELLM_API_BASE` is set, otherwise `openrouter`; the startup loader then lets the last initialized non-OpenRouter client win. `vertex` is not a provider any more — it logs an advisory and falls back to OpenRouter. Reach Gemini through LiteLLM. |
| `OPENROUTER_API_KEY` | OpenRouter key (not HIPAA-eligible). |
| `AWS_BEDROCK_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | Bedrock chat provider. |
| `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_DEPLOYMENT_NAME`, `AZURE_OPENAI_API_VERSION` | Azure OpenAI. |
| `LITELLM_API_BASE`, `LITELLM_API_KEY` | OpenAI-compatible AI gateway (Bifrost, LiteLLM, or similar). `LITELLM_MASTER_KEY` then `OPENAI_API_KEY` are tried if `LITELLM_API_KEY` is unset. |
| `LITELLM_DEFAULT_MODEL` | Default chat model for the LiteLLM provider; falls back to `OPENAI_MODEL`. |
| `LITELLM_FALLBACK_MODEL` | Fallback model within LiteLLM. There is no implicit cross-provider fallback. |
### Speech-to-text
| Variable | Purpose |
|---|---|
| `TRANSCRIBE_PROVIDER` | Use `litellm`; auto mode uses LiteLLM when configured. |
| `TTS_PROVIDER` | Same idea for speech output; `litellm` is the supported value. |
| `LITELLM_STT_MODEL` | Model name for LiteLLM-routed STT. |
### Text-to-speech
| Variable | Purpose |
|---|---|
| `LITELLM_TTS_MODEL`, `LITELLM_TTS_VOICE` | LiteLLM-routed TTS model and default voice. |
| `LITELLM_TTS_VOICES` | Comma-separated LiteLLM-compatible voices exposed in voice search and user preferences. |
### Embeddings
| Variable | Purpose |
|---|---|
| `EMBEDDING_MODEL` | LiteLLM embedding model name (default `openai-text-embedding-3-large`). |
| `EMBEDDING_DIMENSIONS` | Vector dimensions (default 3072). |
### Email (SMTP)
| Variable | Purpose |
|---|---|
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM` | SMTP config for verification + password reset emails. Overridable per-instance via `app_settings`. |
### Security / external
| Variable | Purpose |
|---|---|
| `TURNSTILE_SECRET_KEY` | Cloudflare Turnstile. The verification is a no-op when this is unset. **The matching site key is currently hardcoded in `public/index.html`** (`data-sitekey` on the register and forgot-password widgets), not read from the environment — a different deployment has to edit that file. |
| `LOKI_URL` | Optional Loki ingest URL for shipping audit/api/access logs. |
| `NTFY_URL`, `NTFY_TOKEN` | Optional ntfy push for new-login / password-change notifications. The topic is not configurable — `src/utils/notify.js` derives it per user as `pedscribe-{userId}`. |
| `METRICS_TOKEN` | Lets `/metrics` be scraped from off-host with `Authorization: Bearer <token>`. Without it, any request carrying `X-Forwarded-For` gets a 404, so only same-host scrapes succeed. |
| `API_RATE_LIMIT_MAX` | General request ceiling per IP per minute (default 200). |
| `LOGIN_RATE_LIMIT_MAX` | Sign-in attempts per IP per 15 min (default 10). Raised in the e2e stack so multi-worker Playwright runs do not trip it. |
| `LOGIN_CODE_RATE_LIMIT_MAX` | Emailed sign-in codes per IP per hour (default 5). Lower than the sign-in limit because each request sends mail to somebody else's address. See `docs/authentication.md`. |
### Integrations
| Variable | Purpose |
|---|---|
| `GOTENBERG_URL` | Document conversion service for PDF export (default `http://gotenberg:3000`). PowerPoint and Word are produced in-process and keep working when this is unreachable; only PDF fails. |
| `S3_BUCKET`, `S3_REGION`, `S3_PREFIX`, `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_FORCE_PATH_STYLE` | Document object storage (`S3_PREFIX` defaults to `documents/`). `S3_FORCE_PATH_STYLE=true` for MinIO, Backblaze B2, most non-AWS providers. |
Object storage is resolved per purpose — `documents`, `generated-images`,
`audio-backups` — by `src/utils/objectStorage.js`: the purpose's own variables
first, then the shared `S3_*` ones, then a per-purpose bucket name. Any
credential also accepts a `_FILE` suffix naming a file to read it from, and the
`_FILE` form always wins. Full rules in
[`speech.md`](speech.md#where-object-storage-settings-come-from).
Nextcloud has no environment variables: each user enters their own URL,
username and token in Settings, and those are stored encrypted on the `users`
row.
## `app_settings` — live runtime configuration
Key-value rows in the `app_settings` table. Read via `config.get(key, default)`
with 2-minute in-memory cache. Writes invalidate the cache immediately.
### Registration & site
| Key | Purpose |
|---|---|
| `registration_enabled` | `true`/`false`. Gate new signups. |
| `site.name` | Display name. |
| `site.auto_delete_days` | Days before encounters auto-expire (default 7). |
### Announcements
| Key | Purpose |
|---|---|
| `announcement.text` | Banner text. Empty = banner hidden. |
| `announcement.type` | `info` / `warning` / `error` / `success`. |
### SMTP overrides (override env)
`smtp.host`, `smtp.port`, `smtp.user`, `smtp.pass`, `smtp.from`.
### Email templates
`email.{flow}.subject`, `email.{flow}.body` where `{flow}` is
`verify` / `reset` / `new_login` / `password_changed`.
### OIDC / SSO
| Key | Purpose |
|---|---|
| `oidc.enabled` | Toggle SSO. |
| `oidc.issuer` | OIDC issuer URL. |
| `oidc.client_id`, `oidc.client_secret` | OAuth client credentials. |
| `oidc.button_label` | Login-page button text (default "Sign in with SSO"). |
| `oidc.disable_local_auth` | Hide local login form when SSO is enabled. |
| `oidc.allowed_ips` | CIDR whitelist for SSO (optional). |
### AI / models / prompts
| Key | Purpose |
|---|---|
| `models.default` | Default model ID. |
| `models.disabled` | JSON array of disabled model IDs. |
| `models.custom` | JSON array of admin-added models. |
| `ai.allow_model_fallback` | Enable silent fallback to secondary model on primary failure. **Default false** — fallback could spill to a non-BAA provider. |
| `stt.model`, `tts.model`, `tts.voice` | System-wide STT/TTS defaults (users can override per-account). |
| `prompt.{name}` | Prompt overrides. Any template in `src/utils/prompts.js` can be replaced live. |
### Feature flags
`feature.*` — any key matching this prefix can be consulted via `config.get('feature.foo')`.
### Internal migration flags
| Key | Purpose |
|---|---|
| `migration.text_indexes_c` | Set to `'true'` once lookup-critical text indexes have been converted to `COLLATE "C"`. Prevents re-running. |
## Admin panel
The Admin Panel (`/admin` route, admin-only) exposes everything above plus:
- User list: verify, disable, delete, promote to admin/moderator.
- Session viewer: active sessions per user, admin-revoke.
- Logs: audit / api / access tables with filtering.
- Detailed health: `/api/health/detailed` reports configured providers
(admin-only; the public `/api/health` returns only `{ok: true}` to avoid
leaking stack info).
- Model management: enable/disable, add custom, set default, discover from
provider.
- Prompt editor: live-edit any `PROMPTS.*` key.
- Test SMTP / test STT / test TTS.
## Switching AI gateways
The `LITELLM_API_BASE` and `LITELLM_API_KEY` variables work with any
OpenAI-compatible gateway — LiteLLM, Bifrost, or other proxies.
### Migration steps
1. **Set the base URL**`LITELLM_API_BASE` should include `/v1` if the
gateway serves on that path (e.g., `https://gateway.example.com/v1`).
The application normalizes double `/v1` paths internally for TTS, STT,
and embedding endpoints.
2. **Set the API key**`LITELLM_API_KEY` accepts any key format the
gateway issues (virtual keys, bearer tokens, etc.).
3. **Update model names** — Different gateways use different naming
conventions. Bifrost requires `provider/model` format
(e.g., `openrouter/gpt-4.1`), while LiteLLM can use deployment aliases
(e.g., `openrouter-gpt-4.1`). Update model names in:
- Admin Panel → Models (chat models)
- Admin Panel → Models → Discover & test → Speech / Transcription
(**Set** makes a model the default)
- `LITELLM_TTS_MODEL` env var (if set)
4. **Restart the container**`docker compose up -d --force-recreate` to
pick up `.env` changes (a plain `restart` does not re-read `.env`).
### Verified gateways
| Gateway | Model format | Notes |
|---|---|---|
| Bifrost | `provider/model` | Virtual keys, semantic caching, MCP gateway |
| LiteLLM | Custom aliases | Requires PostgreSQL + Redis |
| Any OpenAI-compatible | Varies | Must serve `/v1/chat/completions`, `/v1/audio/speech`, `/v1/audio/transcriptions`, `/v1/embeddings` |
## Retrieval sizing
How many corpus excerpts the Clinical Assistant and My
Resources each receive, and the reranker cap that overrides all three:
[retrieval-tuning.md](retrieval-tuning.md).