Introduce a document blob lease mechanism to prevent race conditions between document registration and orphaned blob cleanup. The new `document_blob_leases` table ensures that only one process can claim a document blob for mutation or deletion at a time. Update the orphan reaper to acquire a lease before deleting blobs and to re-check for ownership after acquiring the lease, avoiding accidental deletion of in-flight uploads. Enhance scheduled task infrastructure with per-task fencing tokens to prevent stale runners from overwriting newer results, enforce runtime limits with abort signals, and expose scheduler mode and minimum interval to the admin panel and API. Adjust task handlers to accept a context with abort support, and update documentation and environment variable references for the new cron secret and scheduling behavior. BREAKING CHANGE: Scheduled tasks now require a `document_blob_leases` table and updated handler signatures. Vercel deployments must set `CRON_SECRET` for scheduled maintenance.
14 KiB
| title | toc_max_heading_level |
|---|---|
| Environment Variables | 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_BASEis sufficient;API_KEYmay 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:3003orhttps://reader.example.com
AUTH_SECRET
Required secret key used by auth/session handling.
- Required at startup
- Generate with
openssl rand -hex 32
AUTH_TRUSTED_ORIGINS
Additional allowed origins for auth requests.
- Comma-separated list
BASE_URLorigin 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 <CRON_SECRET>on cron invocations. - Generate a strong random value, for example with
openssl rand -hex 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
falseto 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
falseto skip startup migration run
RUN_FS_MIGRATIONS
Controls startup filesystem-to-S3/DB migration pass.
- Default:
true - Set
falseto skip startup storage migration run
Runtime JSON Seed (v4)
RUNTIME_SEED_JSON_PATH
Path-based first-boot seed document.
- If both
RUNTIME_SEED_JSON_PATHandRUNTIME_SEED_JSONare 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_PATHis unset. - Must be a JSON object with
version: 1.
Supported top-level keys:
version(required, must be1)runtimeConfig(optional object, strict-validated against runtime schema)providers(optional array of shared provider seed entries)
Example:
{
"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_KEYfallback is skipped. - If the JSON seed does not include a
providerskey, the legacyAPI_BASE/API_KEYbootstrap fallback can still createdefault-openaiwhen provider rows are empty.API_BASEalone 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
providerssection >API_BASE/API_KEYfallback > no provider bootstrap.