Compare commits
435 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b27c16367 | ||
|
|
dd2c33b01e | ||
|
|
b322339580 | ||
|
|
9ee77408fe | ||
|
|
d4d8f0a590 | ||
|
|
ee979205b3 | ||
|
|
74a96c0d15 | ||
|
|
6b13a3394b | ||
|
|
9520e26a00 | ||
|
|
358a56cc9a | ||
|
|
2abadde949 | ||
|
|
5403aa00cc | ||
|
|
190b67c1d2 | ||
|
|
6e69b48103 | ||
|
|
3d5dfd2a88 | ||
|
|
a56aaa2be9 | ||
|
|
4bfd020125 | ||
|
|
b8621e64fb | ||
|
|
d216ecb713 | ||
|
|
6da2ba2a9a | ||
|
|
801f0dcca2 | ||
|
|
a2e08976b6 | ||
|
|
8ab3a0a159 | ||
|
|
a2f312dc35 | ||
|
|
3258c04ac6 | ||
|
|
2d205008ef | ||
|
|
cecb2489e1 | ||
|
|
7814517b35 | ||
|
|
08b8b06f27 | ||
|
|
11b2f18691 | ||
|
|
56305d5c1b | ||
|
|
c2ff4320bf | ||
|
|
d86df06f9b | ||
|
|
5a44a59ed1 | ||
|
|
ea4bcb5cf0 | ||
|
|
c799a9ece5 | ||
|
|
9f9e612071 | ||
|
|
fd8d462d6e | ||
|
|
5736419bdb | ||
|
|
a284ee852e | ||
|
|
92e93d4231 | ||
|
|
511eeb735c | ||
|
|
5e8d6a6fa8 | ||
|
|
c192d6d729 | ||
|
|
8131b2bee9 | ||
|
|
65c9f03976 | ||
|
|
34b94534cf | ||
|
|
0eec914090 | ||
|
|
d581df6f88 | ||
|
|
335b78b435 | ||
|
|
3f07716b31 | ||
|
|
116b191c9a | ||
|
|
c1e2b5a5f0 | ||
|
|
e85641bcfe | ||
|
|
f09d14549a | ||
|
|
6c12a47f74 | ||
|
|
5a0b40658a | ||
|
|
a2cdc575ef | ||
|
|
ce811f7636 | ||
|
|
ff5a6b834d | ||
|
|
7d99011cf4 | ||
|
|
4714227913 | ||
|
|
cb9ca37500 | ||
|
|
293be5bd28 | ||
|
|
2f5179f10b | ||
|
|
d7a1025977 | ||
|
|
0f6f9cc407 | ||
|
|
e670c38523 | ||
|
|
5185974eb3 | ||
|
|
92df3f907c | ||
|
|
9c685d7590 | ||
|
|
a224efd9e3 | ||
|
|
23b9417567 | ||
|
|
f8182c97df | ||
|
|
925c995274 | ||
|
|
3d1ef1fd41 | ||
|
|
f7c93c90a6 | ||
|
|
379e5082f0 | ||
|
|
4b226858b1 | ||
|
|
fed782eb0b | ||
|
|
922bc32462 | ||
|
|
8071ae1a61 | ||
|
|
112dbc0da9 | ||
|
|
81bd355bd1 | ||
|
|
3f15636e0e | ||
|
|
facbda2477 | ||
|
|
696d3e488f | ||
|
|
07e27430c2 | ||
|
|
328dbbbb62 | ||
|
|
0e29450b0d | ||
|
|
2f99b3987e | ||
|
|
df7e0a5737 | ||
|
|
f89adb6477 | ||
|
|
b594824a8f | ||
|
|
2898e7e5cc | ||
|
|
46c4be614d | ||
|
|
c05a60a228 | ||
|
|
25342371a4 | ||
|
|
9db30742f8 | ||
|
|
7a86ce6a94 | ||
|
|
833f38059c | ||
|
|
9da21a8004 | ||
|
|
e2c0240add | ||
|
|
2e74e79e2c | ||
|
|
c59052a03e | ||
|
|
2ecd281f75 | ||
|
|
7344fcbc51 | ||
|
|
62c913e96d | ||
|
|
d489356b00 | ||
|
|
cc868511d9 | ||
|
|
6c86720926 | ||
|
|
58ad6212c9 | ||
|
|
2e4f36f5c5 | ||
|
|
17c69f074f | ||
|
|
36671f77bb | ||
|
|
224b3ef107 | ||
|
|
e0cd9bc270 | ||
|
|
985c7aaeba | ||
|
|
bb61d0f502 | ||
|
|
1074a60462 | ||
|
|
4317ef03f5 | ||
|
|
2ccc64166f | ||
|
|
f0e0daae77 | ||
|
|
6f89307af4 | ||
|
|
88ed58a6a8 | ||
|
|
beea1961bf | ||
|
|
470898627d | ||
|
|
782a0c3270 | ||
|
|
b41d09c94f | ||
|
|
28f8d50c05 | ||
|
|
68bbd7bc94 | ||
|
|
e295ee4103 | ||
|
|
06206ad5af | ||
|
|
ebe384c698 | ||
|
|
9325f9e467 | ||
|
|
1231b37ef3 | ||
|
|
f2c7760381 | ||
|
|
3d0367bc5b | ||
|
|
e3044278b1 | ||
|
|
3c08b9e406 | ||
|
|
27267e5ffb | ||
|
|
0b1e293638 | ||
|
|
535d562a0b | ||
|
|
14807a0e8c | ||
|
|
e3818db294 | ||
|
|
50234d34a9 | ||
|
|
da1cb1e2be | ||
|
|
2a2ba35454 | ||
|
|
eebb54b018 | ||
|
|
936aa50f9a | ||
|
|
20fa820952 | ||
|
|
5f28be5d58 | ||
|
|
4bcd90274c | ||
|
|
bfe9567310 | ||
|
|
1763a84ffc | ||
|
|
83aa1152cb | ||
|
|
f0800df745 | ||
|
|
f8c2f27e64 | ||
|
|
1b075a35d3 | ||
|
|
b359d66352 | ||
|
|
e6df984646 | ||
|
|
3daf4b6453 | ||
|
|
aa7700e844 | ||
|
|
36fe5f66a3 | ||
|
|
8060ae9944 | ||
|
|
0448835892 | ||
|
|
60c1540744 | ||
|
|
8b45a4f6cd | ||
|
|
3de33e2f1e | ||
|
|
fc3d05d65b | ||
|
|
3307477548 | ||
|
|
22a1b5de57 | ||
|
|
7570181b8a | ||
|
|
b16328abe9 | ||
|
|
e1483ab80a | ||
|
|
b0a4bccf4c | ||
|
|
267e2ce411 | ||
|
|
a0f2d008e0 | ||
|
|
e262f93f90 | ||
|
|
2695c7055b | ||
|
|
d120719abd | ||
|
|
b295d2f38d | ||
|
|
503319d46e | ||
|
|
e62923fb91 | ||
|
|
fe59685454 | ||
|
|
dcec74a4a3 | ||
|
|
5428a21b67 | ||
|
|
c6047ffaa4 | ||
|
|
c5b39f5312 | ||
|
|
8566206911 | ||
|
|
dca1a9b35d | ||
|
|
2f1eafeb79 | ||
|
|
5a684214c8 | ||
|
|
98736bf6e3 | ||
|
|
c12f795026 | ||
|
|
eea143abbf | ||
|
|
e4b0c8358e | ||
|
|
b9490a53ac | ||
|
|
eff25ad8df | ||
|
|
f569c8ded2 | ||
|
|
85aff2c1be | ||
|
|
d59e911ca2 | ||
|
|
495c197c4c | ||
|
|
13e0647b47 | ||
|
|
8104afa3e3 | ||
|
|
53993aa3c6 | ||
|
|
0470f044ef | ||
|
|
46d5bab647 | ||
|
|
eb8820d888 | ||
|
|
7a88dabc53 | ||
|
|
218e248cc2 | ||
|
|
3db193e012 | ||
|
|
139a227aa4 | ||
|
|
f27b5e6504 | ||
|
|
a00caa9052 | ||
|
|
5305b896c6 | ||
|
|
00ce39ed74 | ||
|
|
3da13c2970 | ||
|
|
5de9069c20 | ||
|
|
f78d066434 | ||
|
|
034c1f03a4 | ||
|
|
29cc4fa42d | ||
|
|
59d0101c0c | ||
|
|
7732426963 | ||
|
|
1ec9632d4b | ||
|
|
956ecf5f4a | ||
|
|
5c24ea5763 | ||
|
|
93ae9f3082 | ||
|
|
22f132bbc8 | ||
|
|
14b1b5602f | ||
|
|
c717f7e33e | ||
|
|
ebc1dbcda0 | ||
|
|
16e94a1587 | ||
|
|
cd530a365d | ||
|
|
b2cf4aa7aa | ||
|
|
1de7ed67b4 | ||
|
|
0b1ce1c8dc | ||
|
|
e6e029cf8a | ||
|
|
6f68c169b6 | ||
|
|
bfea95aa35 | ||
|
|
e5ee621288 | ||
|
|
02979a98af | ||
|
|
e3ac1b1dac | ||
|
|
a073fb63a3 | ||
|
|
943e547836 | ||
|
|
0e6f966e3a | ||
|
|
012cb63de6 | ||
|
|
c0f0438fff | ||
|
|
fb0c95dfb1 | ||
|
|
f77ee8cfd4 | ||
|
|
f5eb593554 | ||
|
|
821d0fdc6b | ||
|
|
df05a7d7a3 | ||
|
|
084cbdfac1 | ||
|
|
00fc2d5e36 | ||
|
|
cd06201f5c | ||
|
|
be7ec2c254 | ||
|
|
2df49b54de | ||
|
|
52083c6a09 | ||
|
|
e70b3619e0 | ||
|
|
3f17874fe5 | ||
|
|
b2517bc91d | ||
|
|
12c586d7be | ||
|
|
e75114a943 | ||
|
|
df321b8832 | ||
|
|
65d25e3ce5 | ||
|
|
3dcda2b4b7 | ||
|
|
ad82aede73 | ||
|
|
653dbedaee | ||
|
|
64a68b1011 | ||
|
|
0fcf4e2dab | ||
|
|
b6be71e3b1 | ||
|
|
09e8898683 | ||
|
|
1d4a301a5d | ||
|
|
50c4330ab1 | ||
|
|
b2bb36911a | ||
|
|
2fd9f05652 | ||
|
|
7a49ff9896 | ||
|
|
057d2a4e32 | ||
|
|
55acf05ad6 | ||
|
|
3a2c6ffa42 | ||
|
|
37b6999c2c | ||
|
|
37d11bf9b8 | ||
|
|
1612e6694b | ||
|
|
d8c6d00de6 | ||
|
|
a29e6fc250 | ||
|
|
754fbee227 | ||
|
|
51fbfa117d | ||
|
|
4664806ba0 | ||
|
|
bcdff35200 | ||
|
|
661af71fa4 | ||
|
|
7cdd89b26d | ||
|
|
1f13ecaed9 | ||
|
|
4d12c6913a | ||
|
|
384369f48b | ||
|
|
3ebcb3e6fd | ||
|
|
3c25c93e8b | ||
|
|
c9dad9c23c | ||
|
|
d70c6aa5c6 | ||
|
|
e384b66b7d | ||
|
|
fbb6f484ce | ||
|
|
0d494cf5e5 | ||
|
|
f8672b073e | ||
|
|
671d5c6adb | ||
|
|
a2c714c2a5 | ||
|
|
fe9104e8fc | ||
|
|
08df437e61 | ||
|
|
bde8d74a97 | ||
|
|
dd494d50aa | ||
|
|
10748c7fcd | ||
|
|
3aad8a51e4 | ||
|
|
4497f610c0 | ||
|
|
a7b955e1ca | ||
|
|
8190b57ff2 | ||
|
|
49d9735237 | ||
|
|
086329505f | ||
|
|
91d9fe47a5 | ||
|
|
29b7cfafb9 | ||
|
|
fb32dea73c | ||
|
|
944b8246e5 | ||
|
|
88809b826b | ||
|
|
4ddedee8ec | ||
|
|
1bd13c02fe | ||
|
|
8310091816 | ||
|
|
3a1dd41d91 | ||
|
|
b30304a119 | ||
|
|
4b26531172 | ||
|
|
502c98f11f | ||
|
|
b679bf736c | ||
|
|
bb3eb40966 | ||
|
|
9d1f9d5707 | ||
|
|
0798cd845f | ||
|
|
8439216dd8 | ||
|
|
84c3b22e5a | ||
|
|
01cb95d8e7 | ||
|
|
e52b754106 | ||
|
|
f5d7408f17 | ||
|
|
bab3416a36 | ||
|
|
ea5611c882 | ||
|
|
f1aa1c3e3b | ||
|
|
3a21f2a5f5 | ||
|
|
874e5ef359 | ||
|
|
6ab5c230c2 | ||
|
|
90e7caac43 | ||
|
|
8af4e857b7 | ||
|
|
1544a2d969 | ||
|
|
37a90b734d | ||
|
|
766c04d08d | ||
|
|
d90e48aaf3 | ||
|
|
92762fe858 | ||
|
|
3645b7a327 | ||
|
|
ff62cc702e | ||
|
|
64182e6c8d | ||
|
|
288af0f370 | ||
|
|
80f9386a6c | ||
|
|
ca4228970b | ||
|
|
fb39723089 | ||
|
|
522540452c | ||
|
|
fef3775ad4 | ||
|
|
c3fb3af3d3 | ||
|
|
205d8f37e4 | ||
|
|
1f548f71ea | ||
|
|
0dee3c7b60 | ||
|
|
0787d2e88a | ||
|
|
beb854cb39 | ||
|
|
5efb29cda9 | ||
|
|
9e09f80067 | ||
|
|
f300dfb01f | ||
|
|
df841210a3 | ||
|
|
513128b802 | ||
|
|
da629b3ecd | ||
|
|
ef2ee09064 | ||
|
|
16bf52f889 | ||
|
|
89391410da | ||
|
|
d5eebd3b11 | ||
|
|
e27d20419d | ||
|
|
a296d6fb13 | ||
|
|
fdeeb60f7e | ||
|
|
fd6a06e467 | ||
|
|
2f067c0a90 | ||
|
|
41036e1341 | ||
|
|
3a0ed999ac | ||
|
|
c8a35e505f | ||
|
|
913c6d5d76 | ||
|
|
b4f4d43d6a | ||
|
|
dae97b2afc | ||
|
|
876ca7d774 | ||
|
|
c648d2bf6a | ||
|
|
d7e6a7b7ba | ||
|
|
08d9218ce4 | ||
|
|
8488ad37e1 | ||
|
|
f9adba791d | ||
|
|
e8d4c11434 | ||
|
|
265e1cb56a | ||
|
|
3862d0b0ad | ||
|
|
45ae2f5000 | ||
|
|
4cb482c3f2 | ||
|
|
62103ea752 | ||
|
|
71e1472650 | ||
|
|
71cef7b540 | ||
|
|
222d221e81 | ||
|
|
7d7e933323 | ||
|
|
0a7d392d77 | ||
|
|
9999cafec1 | ||
|
|
f2989efe4d | ||
|
|
6fdcee4f25 | ||
|
|
21030f28eb | ||
|
|
5ea9aa7474 | ||
|
|
8349ef7ae8 | ||
|
|
0c7a845952 | ||
|
|
0db53fbd4e | ||
|
|
67097109ed | ||
|
|
3db3fd19ce | ||
|
|
893f74f038 | ||
|
|
d0c0ab7420 | ||
|
|
9da9232d39 | ||
|
|
6606b0ee4a | ||
|
|
8ea4beac9c | ||
|
|
b7cc2436d2 | ||
|
|
f9237f9270 | ||
|
|
4505684108 | ||
|
|
6d33de83c2 | ||
|
|
bf139d9a67 | ||
|
|
09944ec4e4 | ||
|
|
7d9d8de967 | ||
|
|
dbd202569f | ||
|
|
97dba6d987 | ||
|
|
e373d268d2 | ||
|
|
52e512f17d | ||
|
|
d6ae2baa6f | ||
|
|
4a985b3bf1 | ||
|
|
b600b67252 | ||
|
|
d2f1fe65dd | ||
|
|
e8b5452863 | ||
|
|
0480a52c83 |
1039 changed files with 143812 additions and 14545 deletions
8
.dockerignore
Normal file
8
.dockerignore
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
**/*.creds
|
||||||
|
README.md
|
||||||
|
.next
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
docstore
|
||||||
131
.env.example
131
.env.example
|
|
@ -1,82 +1,95 @@
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
# pretty (default): human-readable logs
|
||||||
|
# json: structured logs for log platforms
|
||||||
|
# LOG_FORMAT=pretty
|
||||||
|
# LOG_LEVEL=info
|
||||||
|
|
||||||
# Local / OpenAI TTS API Configuration (default)
|
# Local / OpenAI TTS API Configuration (default)
|
||||||
# Suggest using https://github.com/remsky/Kokoro-FastAPI
|
# Suggest using https://github.com/remsky/Kokoro-FastAPI
|
||||||
|
#
|
||||||
|
# NOTE: On first boot, the server auto-seeds these values into a "default-openai"
|
||||||
|
# admin-managed shared provider (DB-backed, encrypted at rest). After that, the
|
||||||
|
# admin UI is authoritative and changing these env vars has no effect. You may
|
||||||
|
# remove them once the seed has run. Auth must be configured for this seed path.
|
||||||
|
# See Settings → Admin → Shared providers.
|
||||||
API_BASE=http://localhost:8880/v1
|
API_BASE=http://localhost:8880/v1
|
||||||
API_KEY=api_key_optional
|
API_KEY=
|
||||||
|
|
||||||
# (Optional) TTS request/cache tuning (leave unset to use defaults)
|
# Auth configuration (required in v4+)
|
||||||
# TTS_CACHE_MAX_SIZE_BYTES=268435456 # 256MB
|
|
||||||
# TTS_CACHE_TTL_MS=1800000 # 30 minutes
|
|
||||||
# TTS_MAX_RETRIES=2
|
|
||||||
# TTS_RETRY_INITIAL_MS=250
|
|
||||||
# TTS_RETRY_MAX_MS=2000
|
|
||||||
# TTS_RETRY_BACKOFF=2
|
|
||||||
|
|
||||||
# (Optional) Enable TTS character rate limiting (default is `false`)
|
|
||||||
# TTS_ENABLE_RATE_LIMIT=true
|
|
||||||
# (Optional) TTS per-user daily limits (leave unset to use defaults)
|
|
||||||
# TTS_DAILY_LIMIT_ANONYMOUS=50000
|
|
||||||
# TTS_DAILY_LIMIT_AUTHENTICATED=500000
|
|
||||||
# (Optional) TTS IP backstop daily limits (leave unset to use defaults)
|
|
||||||
# TTS_IP_DAILY_LIMIT_ANONYMOUS=100000
|
|
||||||
# TTS_IP_DAILY_LIMIT_AUTHENTICATED=1000000
|
|
||||||
|
|
||||||
# Auth configuration (recommended for contributors and public instances)
|
|
||||||
# (Optional) Auth is only enabled when AUTH_SECRET and BASE_URL are set
|
|
||||||
BASE_URL=http://localhost:3003 # Externally facing URL for this app (set to LAN IP for access from other devices on the network)
|
BASE_URL=http://localhost:3003 # Externally facing URL for this app (set to LAN IP for access from other devices on the network)
|
||||||
AUTH_SECRET=some_random_secret_key # Generate with `openssl rand -hex 32`
|
AUTH_SECRET=some_random_secret_key # Generate with `openssl rand -base64 32`
|
||||||
AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 # Additional trusted origins (BASE_URL is always trusted)
|
AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 # Additional trusted origins (BASE_URL is always trusted)
|
||||||
# (Optional) Allow anonymous auth sessions when auth is enabled (default: `false`)
|
# (Optional) Allow anonymous auth sessions (default: `false`)
|
||||||
USE_ANONYMOUS_AUTH_SESSIONS=
|
# USE_ANONYMOUS_AUTH_SESSIONS=false
|
||||||
# (Optional) Sign in w/ GitHub Configuration
|
# (Optional) Sign in w/ GitHub Configuration
|
||||||
GITHUB_CLIENT_ID=
|
# GITHUB_CLIENT_ID=
|
||||||
GITHUB_CLIENT_SECRET=
|
# GITHUB_CLIENT_SECRET=
|
||||||
# (Optional) Disable Better Auth built-in rate limiting (useful for testing)
|
|
||||||
DISABLE_AUTH_RATE_LIMIT=
|
|
||||||
|
|
||||||
# (Optional) Backend DB used for server-side metadata (documents/audiobooks) and, when auth is enabled, auth tables.
|
# (Optional) Comma-separated list of emails that are auto-promoted to admin.
|
||||||
|
ADMIN_EMAILS=
|
||||||
|
|
||||||
|
# (Required for the Vercel scheduled-task cron route; Vercel sends it as a bearer token.)
|
||||||
|
# Self-hosted deployments run scheduled tasks in-process and do not require this.
|
||||||
|
# CRON_SECRET=
|
||||||
|
|
||||||
|
# (Optional) Backend DB used for server-side metadata (documents/audiobooks) and auth tables.
|
||||||
# Defaults to SQLite at docstore/sqlite3.db when not set.
|
# Defaults to SQLite at docstore/sqlite3.db when not set.
|
||||||
POSTGRES_URL=
|
# POSTGRES_URL=
|
||||||
|
|
||||||
# Embedded SeaweedFS weed mini config
|
# Embedded SeaweedFS weed mini config
|
||||||
# (Optional) Enable embedded weed mini for local S3-compatible storage (default: `true`)
|
# (Optional) Enable embedded weed mini for local S3-compatible storage (defaults shown below)
|
||||||
USE_EMBEDDED_WEED_MINI=
|
# USE_EMBEDDED_WEED_MINI=true
|
||||||
WEED_MINI_DIR=
|
# WEED_MINI_DIR=docstore/seaweedfs
|
||||||
WEED_MINI_WAIT_SEC=
|
# WEED_MINI_WAIT_SEC=20
|
||||||
|
|
||||||
# S3 storage config (use with embedded weed mini or external S3-compatible storage)
|
# S3 storage config (use with embedded weed mini or external S3-compatible storage)
|
||||||
# (Optional) For embedded weed mini, set explicit keys if you want stable credentials across restarts.
|
# (Optional) For embedded weed mini, set explicit keys if you want stable credentials across restarts.
|
||||||
S3_ACCESS_KEY_ID=
|
S3_ACCESS_KEY_ID=
|
||||||
S3_SECRET_ACCESS_KEY=
|
S3_SECRET_ACCESS_KEY=
|
||||||
S3_BUCKET=
|
S3_BUCKET=
|
||||||
S3_REGION=
|
# S3_REGION=
|
||||||
# (Optional) If empty in embedded mode, OpenReader uses BASE_URL host (when set) or detected LAN host.
|
# (Optional) If empty in embedded mode, OpenReader uses BASE_URL host (when set) or detected LAN host.
|
||||||
S3_ENDPOINT=
|
# S3_ENDPOINT=
|
||||||
S3_FORCE_PATH_STYLE=
|
# S3_FORCE_PATH_STYLE=
|
||||||
S3_PREFIX=
|
# S3_PREFIX=
|
||||||
|
|
||||||
|
# (Optional) Library import mode directory (uses /docstore/library by default)
|
||||||
|
# IMPORT_LIBRARY_DIR=
|
||||||
|
# IMPORT_LIBRARY_DIRS=
|
||||||
|
|
||||||
|
# Compute worker configuration
|
||||||
|
# (Optional) Embedded compute worker (automatic startup) for local compute tasks (defaults shown below)
|
||||||
|
# EMBEDDED_COMPUTE_WORKER_PORT=8081
|
||||||
|
# EMBEDDED_NATS_PORT=4222
|
||||||
|
# EMBEDDED_NATS_MONITOR_PORT=8222
|
||||||
|
# EMBEDDED_NATS_STORE_DIR=docstore/nats/jetstream
|
||||||
|
# NATS_URL=nats://127.0.0.1:4222
|
||||||
|
# COMPUTE_LOG_LEVEL=info
|
||||||
|
# COMPUTE_JOB_CONCURRENCY=1
|
||||||
|
# COMPUTE_WHISPER_TIMEOUT_MS=30000
|
||||||
|
# COMPUTE_PDF_TIMEOUT_MS=300000
|
||||||
|
# COMPUTE_PDF_JOB_ATTEMPTS=1
|
||||||
|
# COMPUTE_OP_STALE_MS=1800000
|
||||||
|
# WHISPER_MODEL_BASE_URL=https://huggingface.co/onnx-community/whisper-base_timestamped/resolve/main
|
||||||
|
# PDF_LAYOUT_MODEL_BASE_URL=https://huggingface.co/Bei0001/PP-DocLayoutV3-ONNX/resolve/main
|
||||||
|
# (Optional) External compute worker config (see docs/deploy/compute-worker)
|
||||||
|
# COMPUTE_WORKER_URL=http://localhost:8081
|
||||||
|
# COMPUTE_WORKER_TOKEN=local-compute-token
|
||||||
|
|
||||||
|
# (Optional) Override ffmpeg binary path used for audio processing
|
||||||
|
# FFMPEG_BIN=
|
||||||
|
|
||||||
|
# (Optional) Test/dev overrides
|
||||||
|
# DISABLE_AUTH_RATE_LIMIT=false
|
||||||
|
# ENABLE_TEST_NAMESPACE=false
|
||||||
|
|
||||||
# Migrations configuration
|
|
||||||
# (Optional) Skip automatic startup migrations when set to `false` (default: `true`)
|
# (Optional) Skip automatic startup migrations when set to `false` (default: `true`)
|
||||||
RUN_DRIZZLE_MIGRATIONS=
|
# RUN_DRIZZLE_MIGRATIONS=true
|
||||||
# (Optional) Skip automatic filesystem->S3/DB migration pass when set to `false` (default: `true`)
|
# (Optional) Skip automatic filesystem->S3/DB migration pass when set to `false` (default: `true`)
|
||||||
RUN_FS_MIGRATIONS=
|
# RUN_FS_MIGRATIONS=true
|
||||||
|
|
||||||
# (Optional) Server library import roots (uses /docstore/library by default)
|
# (Optional) v4 JSON seed for first-boot runtime config + shared providers.
|
||||||
IMPORT_LIBRARY_DIR=
|
# If both are set, RUNTIME_SEED_JSON_PATH is used.
|
||||||
IMPORT_LIBRARY_DIRS=
|
# RUNTIME_SEED_JSON_PATH=/absolute/path/to/openreader-seed.json
|
||||||
|
# RUNTIME_SEED_JSON={"version":1,"runtimeConfig":{"enableUserSignups":true,"restrictUserApiKeys":true,"defaultTtsProvider":"custom-openai","enableTtsProvidersTab":true,"enableAudiobookExport":true,"enableDocxConversion":true,"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}]}
|
||||||
# (Required without Docker) Path to your local whisper.cpp CLI binary for STT timestamp generation
|
|
||||||
WHISPER_CPP_BIN=/whisper.cpp/build/bin/whisper-cli
|
|
||||||
|
|
||||||
# (Optional) Override ffmpeg binary path used for audiobook processing
|
|
||||||
FFMPEG_BIN=
|
|
||||||
|
|
||||||
# (Optional) Client feature flags (does not work in Docker containers due to being build-time only)
|
|
||||||
# NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=true
|
|
||||||
# NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=true
|
|
||||||
# NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=true
|
|
||||||
# NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=custom-openai
|
|
||||||
# NEXT_PUBLIC_DEFAULT_TTS_MODEL=kokoro
|
|
||||||
# NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=true
|
|
||||||
# NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
|
||||||
# NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
|
||||||
|
|
|
||||||
142
.github/workflows/docker-publish.yml
vendored
142
.github/workflows/docker-publish.yml
vendored
|
|
@ -22,30 +22,17 @@ jobs:
|
||||||
prepare:
|
prepare:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
outputs:
|
outputs:
|
||||||
image_name: ${{ steps.image-name.outputs.image_name }}
|
web_image_name: ${{ steps.image-name.outputs.web_image_name }}
|
||||||
legacy_image_name: ${{ steps.image-name.outputs.legacy_image_name }}
|
web_legacy_image_name: ${{ steps.image-name.outputs.web_legacy_image_name }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
compute_worker_image_name: ${{ steps.image-name.outputs.compute_worker_image_name }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Compute Docker image names
|
- name: Compute Docker image names
|
||||||
id: image-name
|
id: image-name
|
||||||
run: |
|
run: |
|
||||||
owner="${GITHUB_REPOSITORY_OWNER,,}"
|
owner="${GITHUB_REPOSITORY_OWNER,,}"
|
||||||
echo "image_name=${owner}/openreader" >> "$GITHUB_OUTPUT"
|
echo "web_image_name=${owner}/openreader" >> "$GITHUB_OUTPUT"
|
||||||
echo "legacy_image_name=${owner}/openreader-webui" >> "$GITHUB_OUTPUT"
|
echo "web_legacy_image_name=${owner}/openreader-webui" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "compute_worker_image_name=${owner}/openreader-compute-worker" >> "$GITHUB_OUTPUT"
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ env.REGISTRY }}/${{ steps.image-name.outputs.image_name }}
|
|
||||||
${{ env.REGISTRY }}/${{ steps.image-name.outputs.legacy_image_name }}
|
|
||||||
tags: |
|
|
||||||
type=raw,value=latest,enable=${{ (github.event_name == 'push' && !contains(github.ref, '-pre')) || (github.event_name == 'workflow_dispatch' && inputs.use_latest_tag == true) }},priority=1000
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=ref,event=tag
|
|
||||||
type=ref,event=branch,enable=${{ github.event_name == 'workflow_dispatch' && inputs.use_latest_tag != true }}
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: prepare
|
needs: prepare
|
||||||
|
|
@ -58,96 +45,157 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- arch: amd64
|
- image_target: web
|
||||||
|
arch: amd64
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
runner: ubuntu-24.04
|
runner: ubuntu-24.04
|
||||||
- arch: arm64
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
- image_target: web
|
||||||
|
arch: arm64
|
||||||
platform: linux/arm64
|
platform: linux/arm64
|
||||||
runner: ubuntu-24.04-arm
|
runner: ubuntu-24.04-arm
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
- image_target: compute-worker
|
||||||
|
arch: amd64
|
||||||
|
platform: linux/amd64
|
||||||
|
runner: ubuntu-24.04
|
||||||
|
context: .
|
||||||
|
dockerfile: ./packages/compute-worker/Dockerfile
|
||||||
|
- image_target: compute-worker
|
||||||
|
arch: arm64
|
||||||
|
platform: linux/arm64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
context: .
|
||||||
|
dockerfile: ./packages/compute-worker/Dockerfile
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.sha }}
|
ref: ${{ github.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Docker image (${{ matrix.arch }})
|
- name: Select image name
|
||||||
|
id: image
|
||||||
|
run: |
|
||||||
|
if [ "${{ matrix.image_target }}" = "web" ]; then
|
||||||
|
echo "image_name=${{ needs.prepare.outputs.web_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "image_name=${{ needs.prepare.outputs.compute_worker_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push Docker image (${{ matrix.image_target }} / ${{ matrix.arch }})
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: ${{ matrix.context }}
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
platforms: ${{ matrix.platform }}
|
platforms: ${{ matrix.platform }}
|
||||||
labels: ${{ needs.prepare.outputs.labels }}
|
cache-from: type=gha,scope=${{ matrix.image_target }}-${{ matrix.arch }}
|
||||||
cache-from: type=gha,scope=${{ matrix.arch }}
|
cache-to: type=gha,mode=max,scope=${{ matrix.image_target }}-${{ matrix.arch }}
|
||||||
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
|
|
||||||
provenance: false
|
provenance: false
|
||||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ needs.prepare.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
|
outputs: type=image,name=${{ env.REGISTRY }}/${{ steps.image.outputs.image_name }},push-by-digest=true,name-canonical=true,push=true
|
||||||
|
|
||||||
- name: Export digest
|
- name: Export digest
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/digests
|
mkdir -p /tmp/digests/${{ matrix.image_target }}
|
||||||
digest="${{ steps.build.outputs.digest }}"
|
digest="${{ steps.build.outputs.digest }}"
|
||||||
touch "/tmp/digests/${digest#sha256:}"
|
touch "/tmp/digests/${{ matrix.image_target }}/${digest#sha256:}"
|
||||||
|
|
||||||
- name: Upload digest
|
- name: Upload digest
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: digests-${{ matrix.arch }}
|
name: digests-${{ matrix.image_target }}-${{ matrix.arch }}
|
||||||
path: /tmp/digests
|
path: /tmp/digests/${{ matrix.image_target }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
merge:
|
merge:
|
||||||
needs: [prepare, build]
|
needs: [prepare, build]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ${{ matrix.runner }}
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- image_target: web
|
||||||
|
runner: ubuntu-24.04
|
||||||
|
- image_target: compute-worker
|
||||||
|
runner: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download digests
|
- name: Download digests
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
path: /tmp/digests
|
path: /tmp/digests/${{ matrix.image_target }}
|
||||||
pattern: digests-*
|
pattern: digests-${{ matrix.image_target }}-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Select image names
|
||||||
|
id: image
|
||||||
|
run: |
|
||||||
|
if [ "${{ matrix.image_target }}" = "web" ]; then
|
||||||
|
echo "image_name=${{ needs.prepare.outputs.web_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "metadata_images<<EOF" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "${{ env.REGISTRY }}/${{ needs.prepare.outputs.web_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "${{ env.REGISTRY }}/${{ needs.prepare.outputs.web_legacy_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "EOF" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "image_name=${{ needs.prepare.outputs.compute_worker_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "metadata_images=${{ env.REGISTRY }}/${{ needs.prepare.outputs.compute_worker_image_name }}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v6
|
||||||
|
with:
|
||||||
|
images: ${{ steps.image.outputs.metadata_images }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest,enable=${{ (github.event_name == 'push' && !contains(github.ref, '-pre')) || (github.event_name == 'workflow_dispatch' && inputs.use_latest_tag == true) }},priority=1000
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=ref,event=tag
|
||||||
|
type=ref,event=branch,enable=${{ github.event_name == 'workflow_dispatch' && inputs.use_latest_tag != true }}
|
||||||
|
|
||||||
- name: Create manifest list and push
|
- name: Create manifest list and push
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests/${{ matrix.image_target }}
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
$(echo "$TAGS" | xargs -I {} echo -t {}) \
|
$(echo "$TAGS" | xargs -I {} echo -t {}) \
|
||||||
$(printf '${{ env.REGISTRY }}/${{ needs.prepare.outputs.image_name }}@sha256:%s ' *)
|
$(printf '${{ env.REGISTRY }}/${{ steps.image.outputs.image_name }}@sha256:%s ' *)
|
||||||
env:
|
env:
|
||||||
TAGS: ${{ needs.prepare.outputs.tags }}
|
TAGS: ${{ steps.meta.outputs.tags }}
|
||||||
|
|
||||||
- name: Output build information
|
- name: Output build information
|
||||||
run: |
|
run: |
|
||||||
echo "✅ Docker images built and pushed successfully!"
|
echo "✅ Docker image built and pushed successfully!"
|
||||||
|
echo "🐋 Target: ${{ matrix.image_target }}"
|
||||||
echo "🐋 Images:"
|
echo "🐋 Images:"
|
||||||
echo '${{ needs.prepare.outputs.tags }}' | sed 's/^/ - /'
|
echo '${{ steps.meta.outputs.tags }}' | sed 's/^/ - /'
|
||||||
echo "📝 Event: ${{ github.event_name }}"
|
echo "📝 Event: ${{ github.event_name }}"
|
||||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||||
echo "📝 Triggered by manual workflow dispatch on branch: ${{ github.ref_name }}"
|
echo "📝 Triggered by manual workflow dispatch on branch: ${{ github.ref_name }}"
|
||||||
|
|
|
||||||
16
.github/workflows/docs-check.yml
vendored
16
.github/workflows/docs-check.yml
vendored
|
|
@ -16,22 +16,22 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: lts/*
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: docs-site/pnpm-lock.yaml
|
cache-dependency-path: docs-site/pnpm-lock.yaml
|
||||||
|
|
||||||
- name: Install docs dependencies
|
- name: Install docs dependencies
|
||||||
run: pnpm --dir docs-site install --frozen-lockfile
|
working-directory: docs-site
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: pnpm --dir docs-site build
|
working-directory: docs-site
|
||||||
|
run: pnpm build
|
||||||
|
|
|
||||||
34
.github/workflows/docs-deploy-on-release.yml
vendored
Normal file
34
.github/workflows/docs-deploy-on-release.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
name: Docs Deploy On Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published, edited, deleted]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: docs-release-relay-${{ github.event.release.tag_name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
relay:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Dispatch Docs Deploy on main
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
TAG: ${{ github.event.release.tag_name }}
|
||||||
|
ACTION: ${{ github.event.action }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
gh api \
|
||||||
|
-X POST \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"repos/${REPO}/actions/workflows/docs-deploy.yml/dispatches" \
|
||||||
|
-f ref=main \
|
||||||
|
-f "inputs[full_reconcile]=false" \
|
||||||
|
-f "inputs[release_tag]=${TAG}" \
|
||||||
|
-f "inputs[release_action]=${ACTION}"
|
||||||
45
.github/workflows/docs-deploy.yml
vendored
45
.github/workflows/docs-deploy.yml
vendored
|
|
@ -3,10 +3,21 @@ name: Docs Deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
workflow_run:
|
|
||||||
workflows: ["Version Docs on Tag"]
|
|
||||||
types: [completed]
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
full_reconcile:
|
||||||
|
description: 'Run full changelog reconcile from GitHub Releases'
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
release_tag:
|
||||||
|
description: 'Optional release tag for incremental changelog sync (for release relay)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
release_action:
|
||||||
|
description: 'Optional release action (published, edited, deleted) for incremental sync'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -19,35 +30,41 @@ concurrency:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: lts/*
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: docs-site/pnpm-lock.yaml
|
cache-dependency-path: docs-site/pnpm-lock.yaml
|
||||||
|
|
||||||
- name: Install docs dependencies
|
- name: Install docs dependencies
|
||||||
run: pnpm --dir docs-site install --frozen-lockfile
|
working-directory: docs-site
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build changelog feed
|
||||||
|
env:
|
||||||
|
CHANGELOG_PUBLIC_BASE_URL: https://docs.openreader.richardr.dev
|
||||||
|
CHANGELOG_FORCE_FULL: ${{ github.event_name == 'workflow_dispatch' && inputs.full_reconcile && '1' || '0' }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: node scripts/build-changelog-feed.mjs
|
||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: pnpm --dir docs-site build
|
working-directory: docs-site
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
- name: Upload Pages artifact
|
- name: Upload Pages artifact
|
||||||
uses: actions/upload-pages-artifact@v3
|
uses: actions/upload-pages-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: docs-site/build
|
path: docs-site/build
|
||||||
|
|
||||||
|
|
@ -61,4 +78,4 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v5
|
||||||
|
|
|
||||||
21
.github/workflows/docs-version-on-tag.yml
vendored
21
.github/workflows/docs-version-on-tag.yml
vendored
|
|
@ -24,39 +24,40 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.sha }}
|
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.sha }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v6
|
||||||
with:
|
|
||||||
version: 9
|
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: lts/*
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: docs-site/pnpm-lock.yaml
|
cache-dependency-path: docs-site/pnpm-lock.yaml
|
||||||
|
|
||||||
- name: Install docs dependencies
|
- name: Install docs dependencies
|
||||||
run: pnpm --dir docs-site install --frozen-lockfile
|
working-directory: docs-site
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Snapshot docs version
|
- name: Snapshot docs version
|
||||||
|
working-directory: docs-site
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }}
|
VERSION: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
if [ -f docs-site/versions.json ] && grep -q "\"${VERSION}\"" docs-site/versions.json; then
|
if [ -f versions.json ] && grep -q "\"${VERSION}\"" versions.json; then
|
||||||
echo "Version ${VERSION} already exists in versions.json; skipping docs:version"
|
echo "Version ${VERSION} already exists in versions.json; skipping docs:version"
|
||||||
else
|
else
|
||||||
pnpm --dir docs-site docusaurus docs:version "${VERSION}"
|
pnpm docusaurus docs:version "${VERSION}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: pnpm --dir docs-site build
|
working-directory: docs-site
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
- name: Commit docs snapshot
|
- name: Commit docs snapshot
|
||||||
id: commit_snapshot
|
id: commit_snapshot
|
||||||
|
|
|
||||||
41
.github/workflows/playwright.yml
vendored
41
.github/workflows/playwright.yml
vendored
|
|
@ -4,38 +4,51 @@ on:
|
||||||
branches: [ main, master ]
|
branches: [ main, master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main, master ]
|
branches: [ main, master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
e2e-testing:
|
e2e-testing:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 30
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
FFMPEG_BIN: /usr/bin/ffmpeg
|
|
||||||
USE_EMBEDDED_WEED_MINI: true
|
USE_EMBEDDED_WEED_MINI: true
|
||||||
|
ENABLE_TEST_NAMESPACE: true
|
||||||
BASE_URL: http://127.0.0.1:3003
|
BASE_URL: http://127.0.0.1:3003
|
||||||
|
AUTH_SECRET: ci-auth-secret-change-me
|
||||||
|
USE_ANONYMOUS_AUTH_SESSIONS: true
|
||||||
S3_ENDPOINT: http://127.0.0.1:8333
|
S3_ENDPOINT: http://127.0.0.1:8333
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
- uses: pnpm/action-setup@v4
|
package-manager-cache: false
|
||||||
with:
|
- uses: pnpm/action-setup@v6
|
||||||
version: 9
|
|
||||||
- name: Install system dependencies
|
- name: Install system dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libreoffice-writer ffmpeg
|
sudo apt-get install -y libreoffice-writer ffmpeg
|
||||||
- name: Install SeaweedFS weed binary
|
- name: Install SeaweedFS weed binary
|
||||||
run: |
|
run: |
|
||||||
WEED_PATH=$(docker run --rm --entrypoint sh chrislusf/seaweedfs:latest -lc 'command -v weed')
|
WEED_PATH=$(docker run --rm --entrypoint sh chrislusf/seaweedfs:4.18 -lc 'command -v weed')
|
||||||
CID=$(docker create chrislusf/seaweedfs:latest)
|
CID=$(docker create chrislusf/seaweedfs:4.18)
|
||||||
docker cp "${CID}:${WEED_PATH}" ./weed
|
docker cp "${CID}:${WEED_PATH}" ./weed
|
||||||
docker rm "${CID}"
|
docker rm "${CID}"
|
||||||
sudo install -m 0755 ./weed /usr/local/bin/weed
|
sudo install -m 0755 ./weed /usr/local/bin/weed
|
||||||
rm -f ./weed
|
rm -f ./weed
|
||||||
weed version
|
weed version
|
||||||
|
- name: Install NATS server binary
|
||||||
|
run: |
|
||||||
|
curl -fsSL -o /tmp/nats-server.tar.gz \
|
||||||
|
https://github.com/nats-io/nats-server/releases/download/v2.12.1/nats-server-v2.12.1-linux-amd64.tar.gz
|
||||||
|
tar -xzf /tmp/nats-server.tar.gz -C /tmp
|
||||||
|
sudo install -m 0755 /tmp/nats-server-v2.12.1-linux-amd64/nats-server /usr/local/bin/nats-server
|
||||||
|
nats-server -v
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
- name: Build app and enforce bundle guard
|
||||||
|
run: |
|
||||||
|
pnpm build
|
||||||
|
pnpm build:bundle-guard
|
||||||
- name: Verify ffprobe
|
- name: Verify ffprobe
|
||||||
run: ffprobe -version
|
run: ffprobe -version
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
|
|
@ -44,10 +57,12 @@ jobs:
|
||||||
env:
|
env:
|
||||||
API_BASE: https://api.deepinfra.com/v1/openai
|
API_BASE: https://api.deepinfra.com/v1/openai
|
||||||
API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||||
run: pnpm exec playwright test --reporter=list,github,html
|
run: pnpm exec playwright test --max-failures=5 --reporter=list,github,html
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v7
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: playwright-report/
|
path: |
|
||||||
|
playwright-report/
|
||||||
|
tests/results/
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
|
||||||
29
.github/workflows/vitest.yml
vendored
Normal file
29
.github/workflows/vitest.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
name: Vitest Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
vitest-testing:
|
||||||
|
timeout-minutes: 20
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
package-manager-cache: false
|
||||||
|
- uses: pnpm/action-setup@v6
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
- name: Apply drizzle migrations (SQLite)
|
||||||
|
run: pnpm migrate
|
||||||
|
- name: Run Vitest suites
|
||||||
|
run: pnpm test:unit
|
||||||
|
- name: Verify compute worker contract and boundary
|
||||||
|
run: pnpm compute:openapi:check && pnpm check:compute-boundary
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -32,8 +32,9 @@ yarn-error.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env
|
.env*
|
||||||
.dockerignore
|
!.env.example
|
||||||
|
*.creds
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
@ -55,5 +56,7 @@ node_modules/
|
||||||
# vscode
|
# vscode
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# .agents
|
# Agents
|
||||||
.agents
|
.agents
|
||||||
|
.codex
|
||||||
|
.claude
|
||||||
|
|
|
||||||
78
Dockerfile
78
Dockerfile
|
|
@ -1,35 +1,28 @@
|
||||||
# Stage 1: build whisper.cpp (no model download – the app handles that)
|
# Stage 1: extract seaweedfs weed binary (for optional embedded weed mini)
|
||||||
FROM alpine:3.23 AS whisper-builder
|
# Pin to 4.18 because CI observed upload regressions on 4.19.
|
||||||
|
FROM chrislusf/seaweedfs:4.18 AS seaweedfs-builder
|
||||||
RUN apk add --no-cache git cmake build-base
|
|
||||||
|
|
||||||
WORKDIR /opt
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
RUN git clone --depth 1 https://github.com/ggml-org/whisper.cpp.git && \
|
|
||||||
cd whisper.cpp && \
|
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DGGML_NATIVE=OFF $( [ "$TARGETARCH" = "arm64" ] && echo "-DGGML_CPU_ARM_ARCH=armv8-a" || true ) && \
|
|
||||||
cmake --build build -j
|
|
||||||
|
|
||||||
# Stage 1b: extract seaweedfs weed binary (for optional embedded weed mini)
|
|
||||||
FROM chrislusf/seaweedfs:latest AS seaweedfs-builder
|
|
||||||
RUN cp "$(command -v weed)" /tmp/weed && \
|
RUN cp "$(command -v weed)" /tmp/weed && \
|
||||||
(wget -qO /tmp/SeaweedFS-LICENSE.txt "https://raw.githubusercontent.com/seaweedfs/seaweedfs/master/LICENSE" || \
|
(wget -qO /tmp/SeaweedFS-LICENSE.txt "https://raw.githubusercontent.com/seaweedfs/seaweedfs/master/LICENSE" || \
|
||||||
wget -qO /tmp/SeaweedFS-LICENSE.txt "https://raw.githubusercontent.com/seaweedfs/seaweedfs/main/LICENSE")
|
wget -qO /tmp/SeaweedFS-LICENSE.txt "https://raw.githubusercontent.com/seaweedfs/seaweedfs/main/LICENSE")
|
||||||
|
|
||||||
|
# Stage 1b: extract nats-server binary for embedded single-container worker mode.
|
||||||
|
FROM nats:2.11-alpine AS nats-builder
|
||||||
|
RUN cp "$(command -v nats-server)" /tmp/nats-server
|
||||||
|
|
||||||
# Stage 2: build the Next.js app
|
# Stage 2: build the Next.js app
|
||||||
FROM node:lts-alpine AS app-builder
|
FROM node:lts-slim AS app-builder
|
||||||
|
|
||||||
# Install pnpm globally
|
# Install pnpm globally
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm@10.33.4
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy workspace manifests needed for dependency installation
|
||||||
COPY package.json pnpm-lock.yaml ./
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
|
COPY packages/bootstrap/package.json ./packages/bootstrap/package.json
|
||||||
|
COPY packages/compute-worker/package.json ./packages/compute-worker/package.json
|
||||||
|
COPY packages/database/package.json ./packages/database/package.json
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
@ -39,7 +32,10 @@ COPY . .
|
||||||
|
|
||||||
# Build the Next.js application
|
# Build the Next.js application
|
||||||
RUN pnpm exec next telemetry disable
|
RUN pnpm exec next telemetry disable
|
||||||
RUN pnpm build
|
RUN AUTH_SECRET=build-placeholder-secret-value-32chars!! BASE_URL=http://localhost:3003 pnpm build
|
||||||
|
RUN pnpm --config.inject-workspace-packages=true --filter @openreader/bootstrap deploy /opt/openreader/bootstrap
|
||||||
|
RUN pnpm --dir /opt/openreader/bootstrap rebuild better-sqlite3 ffmpeg-static
|
||||||
|
RUN pnpm --config.inject-workspace-packages=true --filter @openreader/compute-worker deploy /opt/openreader/embedded-compute-worker
|
||||||
# Generate third-party dependency license report plus copied license files.
|
# Generate third-party dependency license report plus copied license files.
|
||||||
RUN mkdir -p /app/THIRD_PARTY_LICENSES && \
|
RUN mkdir -p /app/THIRD_PARTY_LICENSES && \
|
||||||
pnpm dlx license-checker-rseidelsohn@4.3.0 \
|
pnpm dlx license-checker-rseidelsohn@4.3.0 \
|
||||||
|
|
@ -51,40 +47,50 @@ RUN mkdir -p /app/THIRD_PARTY_LICENSES && \
|
||||||
|
|
||||||
|
|
||||||
# Stage 3: minimal runtime image
|
# Stage 3: minimal runtime image
|
||||||
FROM node:lts-alpine AS runner
|
FROM node:lts-slim AS runner
|
||||||
|
|
||||||
# Add runtime OS dependencies:
|
# Add runtime OS dependencies:
|
||||||
# - libreoffice-writer: required for DOCX → PDF conversion
|
# - libreoffice-writer: required for DOCX → PDF conversion
|
||||||
# ffmpeg is provided by ffmpeg-static from node_modules.
|
# ffmpeg is provided by ffmpeg-static from node_modules.
|
||||||
RUN apk add --no-cache ca-certificates libreoffice-writer
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ca-certificates libreoffice-writer && \
|
||||||
# Install pnpm globally for running the app
|
rm -rf /var/lib/apt/lists/*
|
||||||
RUN npm install -g pnpm
|
|
||||||
|
|
||||||
# App runtime directory
|
# App runtime directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy built app and dependencies from the builder stage
|
# Copy only the standalone Next runtime and assets.
|
||||||
COPY --from=app-builder /app ./
|
COPY --from=app-builder /app/.next/standalone ./
|
||||||
|
COPY --from=app-builder /app/.next/static ./.next/static
|
||||||
|
COPY --from=app-builder /app/public ./public
|
||||||
|
|
||||||
|
# Ship startup orchestration and the embedded worker as independent deployed bundles.
|
||||||
|
COPY --from=app-builder /opt/openreader/bootstrap /opt/openreader/bootstrap
|
||||||
|
COPY --from=app-builder /opt/openreader/embedded-compute-worker /opt/openreader/embedded-compute-worker
|
||||||
# Include third-party license report and copied license texts at a stable path in the image.
|
# Include third-party license report and copied license texts at a stable path in the image.
|
||||||
COPY --from=app-builder /app/THIRD_PARTY_LICENSES /licenses
|
COPY --from=app-builder /app/THIRD_PARTY_LICENSES /licenses
|
||||||
# Include SeaweedFS license text for the copied weed binary.
|
# Include SeaweedFS license text for the copied weed binary.
|
||||||
COPY --from=seaweedfs-builder /tmp/SeaweedFS-LICENSE.txt /licenses/SeaweedFS-LICENSE.txt
|
COPY --from=seaweedfs-builder /tmp/SeaweedFS-LICENSE.txt /licenses/SeaweedFS-LICENSE.txt
|
||||||
|
# Include static model notices for runtime-downloaded assets.
|
||||||
|
COPY --from=app-builder /app/packages/compute-worker/src/inference/pdf/assets/LICENSE.txt /licenses/pp-doclayoutv3-LICENSE.txt
|
||||||
|
|
||||||
# Copy the compiled whisper.cpp build output into the runtime image
|
|
||||||
# (includes whisper-cli and its shared libraries, e.g. libwhisper.so, libggml.so)
|
|
||||||
COPY --from=whisper-builder /opt/whisper.cpp/build /opt/whisper.cpp/build
|
|
||||||
# Copy seaweedfs weed binary for optional embedded local S3.
|
# Copy seaweedfs weed binary for optional embedded local S3.
|
||||||
COPY --from=seaweedfs-builder /tmp/weed /usr/local/bin/weed
|
COPY --from=seaweedfs-builder /tmp/weed /usr/local/bin/weed
|
||||||
RUN chmod +x /usr/local/bin/weed
|
RUN chmod +x /usr/local/bin/weed
|
||||||
|
# Copy nats-server binary for embedded local JetStream.
|
||||||
|
COPY --from=nats-builder /tmp/nats-server /usr/local/bin/nats-server
|
||||||
|
RUN chmod +x /usr/local/bin/nats-server
|
||||||
|
|
||||||
# Point the app at the compiled whisper-cli binary and ensure its libs are discoverable
|
# Include OpenAI Whisper license text for runtime-downloaded ONNX artifacts.
|
||||||
ENV WHISPER_CPP_BIN=/opt/whisper.cpp/build/bin/whisper-cli
|
COPY --from=app-builder /app/packages/compute-worker/src/inference/whisper/assets/LICENSE.txt /licenses/openai-whisper-LICENSE.txt
|
||||||
ENV LD_LIBRARY_PATH=/opt/whisper.cpp/build
|
|
||||||
|
# Match the app's historical container port now that standalone server.js
|
||||||
|
# is started directly instead of `next start -p 3003`.
|
||||||
|
ENV PORT=3003
|
||||||
|
|
||||||
# Expose the port the app runs on
|
# Expose the port the app runs on
|
||||||
EXPOSE 3003
|
EXPOSE 3003
|
||||||
|
|
||||||
# Start the application
|
# Start the application
|
||||||
ENTRYPOINT ["node", "scripts/openreader-entrypoint.mjs", "--"]
|
ENTRYPOINT ["node", "/opt/openreader/bootstrap/src/cli.mjs", "--"]
|
||||||
CMD ["pnpm", "start:raw"]
|
CMD ["node", "server.js"]
|
||||||
|
|
|
||||||
16
README.md
16
README.md
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
# 📄🔊 OpenReader
|
# 📄🔊 OpenReader
|
||||||
|
|
||||||
OpenReader is an open source, self-host-friendly text-to-speech document reader built with Next.js for **EPUB, PDF, TXT, MD, and DOCX** with synchronized read-along playback.
|
OpenReader is an open-source, self-host-friendly text-to-speech document reader built with Next.js for **EPUB, PDF, TXT, MD, and DOCX** with multilingual, synchronized read-along playback.
|
||||||
|
|
||||||
> Previously named **OpenReader-WebUI**.
|
> Previously named **OpenReader-WebUI**.
|
||||||
|
|
||||||
|
|
@ -18,13 +18,14 @@ OpenReader is an open source, self-host-friendly text-to-speech document reader
|
||||||
|
|
||||||
## ✨ Highlights
|
## ✨ Highlights
|
||||||
|
|
||||||
- 🎯 **Multi-provider TTS** with OpenAI-compatible endpoints (OpenAI, DeepInfra, Kokoro, KittenTTS-FastAPI, Orpheus, custom).
|
- 🧱 **Layout-aware PDF parsing** with PP-DocLayoutV3 (ONNX) — structured block detection, cross-page stitching, and geometry-based highlighting for precise read-along sync.
|
||||||
- 📖 **Read-along playback** for PDF/EPUB with sentence-aware narration.
|
- ⏱️ **Word-by-word highlighting** via ONNX Whisper alignment through the compute worker control plane (NATS JetStream-backed).
|
||||||
- ⏱️ **Word-by-word highlighting** via optional `whisper.cpp` timestamps.
|
- ⚡ **Segment-based read-along** for EPUB, PDF, TXT, MD, and DOCX — sentence-aware TTS with cached audio segments, background preloading, and resumable playback.
|
||||||
- 🛜 **Sync + library import** to bring docs across devices and from server-mounted folders.
|
- 🎯 **Multi-provider TTS** — self-hosted OpenAI-compatible servers (Kokoro-FastAPI, KittenTTS-FastAPI, Orpheus-FastAPI) or cloud APIs (OpenAI, Replicate, DeepInfra).
|
||||||
- 🗂️ **Flexible storage** with embedded SeaweedFS or external S3-compatible backends.
|
- 🌐 **Multilingual support** — choose a document language for language-aware narration and highlighting. Available languages depend on the configured TTS provider and voice.
|
||||||
- 🎧 **Audiobook export** in `m4b`/`mp3` with resumable chapter processing.
|
- 🎧 **Audiobook export** in `m4b`/`mp3` with resumable chapter processing.
|
||||||
- 🐳 **Self-host friendly** with Docker, optional auth, and automatic startup migrations.
|
- 🗂️ **Flexible backend** — embedded SeaweedFS or S3-compatible storage, SQLite or Postgres, server library import, and device sync.
|
||||||
|
- 🐳 **Self-host friendly** — Docker (amd64/arm64), built-in auth/session support, and automatic startup migrations.
|
||||||
|
|
||||||
## 🚀 Start Here
|
## 🚀 Start Here
|
||||||
|
|
||||||
|
|
@ -32,6 +33,7 @@ OpenReader is an open source, self-host-friendly text-to-speech document reader
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Run with Docker | [Docker Quick Start](https://docs.openreader.richardr.dev/docker-quick-start) |
|
| Run with Docker | [Docker Quick Start](https://docs.openreader.richardr.dev/docker-quick-start) |
|
||||||
| Deploy on Vercel | [Vercel Deployment](https://docs.openreader.richardr.dev/deploy/vercel-deployment) |
|
| Deploy on Vercel | [Vercel Deployment](https://docs.openreader.richardr.dev/deploy/vercel-deployment) |
|
||||||
|
| Deploy external compute worker | [Compute Worker (NATS JetStream)](https://docs.openreader.richardr.dev/deploy/compute-worker) |
|
||||||
| Develop locally | [Local Development](https://docs.openreader.richardr.dev/deploy/local-development) |
|
| Develop locally | [Local Development](https://docs.openreader.richardr.dev/deploy/local-development) |
|
||||||
| Configure auth | [Auth](https://docs.openreader.richardr.dev/configure/auth) |
|
| Configure auth | [Auth](https://docs.openreader.richardr.dev/configure/auth) |
|
||||||
| Configure SQL database | [Database and Migrations](https://docs.openreader.richardr.dev/configure/database) |
|
| Configure SQL database | [Database and Migrations](https://docs.openreader.richardr.dev/configure/database) |
|
||||||
|
|
|
||||||
124
docker/examples/compose.full.yml
Normal file
124
docker/examples/compose.full.yml
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
name: openreader-full
|
||||||
|
|
||||||
|
services:
|
||||||
|
openreader:
|
||||||
|
image: ghcr.io/richardr1126/openreader:latest
|
||||||
|
# Keep localhost:8333 valid for both app requests and browser-facing presigned URLs.
|
||||||
|
network_mode: service:seaweedfs
|
||||||
|
depends_on:
|
||||||
|
kokoro-tts:
|
||||||
|
condition: service_started
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
BASE_URL: ${BASE_URL:-http://localhost:3003}
|
||||||
|
AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me}
|
||||||
|
POSTGRES_URL: postgres://openreader:openreader@postgres:5432/openreader
|
||||||
|
COMPUTE_WORKER_URL: http://compute-worker:8081
|
||||||
|
COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token}
|
||||||
|
USE_EMBEDDED_WEED_MINI: "false"
|
||||||
|
S3_ENDPOINT: ${S3_ENDPOINT:-http://localhost:8333} # Used for internal endpoint and public facing presigned URLs.
|
||||||
|
S3_BUCKET: ${S3_BUCKET:-openreader-documents}
|
||||||
|
S3_REGION: ${S3_REGION:-us-east-1}
|
||||||
|
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret}
|
||||||
|
S3_FORCE_PATH_STYLE: "true"
|
||||||
|
S3_PREFIX: ${S3_PREFIX:-openreader}
|
||||||
|
RUNTIME_SEED_JSON: |-
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"runtimeConfig": {
|
||||||
|
"defaultTtsProvider": "kokoro"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"slug": "kokoro",
|
||||||
|
"displayName": "Kokoro",
|
||||||
|
"providerType": "custom-openai",
|
||||||
|
"baseUrl": "http://kokoro-tts:8880/v1",
|
||||||
|
"defaultModel": "kokoro",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
volumes:
|
||||||
|
- openreader-docstore:/app/docstore
|
||||||
|
|
||||||
|
kokoro-tts:
|
||||||
|
image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
|
||||||
|
environment:
|
||||||
|
ONNX_NUM_THREADS: 8
|
||||||
|
ONNX_INTER_OP_THREADS: 4
|
||||||
|
ONNX_EXECUTION_MODE: parallel
|
||||||
|
ONNX_OPTIMIZATION_LEVEL: all
|
||||||
|
ONNX_MEMORY_PATTERN: "true"
|
||||||
|
ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo
|
||||||
|
API_LOG_LEVEL: DEBUG
|
||||||
|
ports:
|
||||||
|
- "8880:8880"
|
||||||
|
|
||||||
|
seaweedfs:
|
||||||
|
image: chrislusf/seaweedfs:4.18
|
||||||
|
command: ["mini", "-dir=/data"]
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 30
|
||||||
|
ports:
|
||||||
|
- "3003:3003"
|
||||||
|
- "8333:8333"
|
||||||
|
volumes:
|
||||||
|
- seaweedfs-data:/data
|
||||||
|
|
||||||
|
nats:
|
||||||
|
image: nats:2.14-alpine
|
||||||
|
command: ["-js", "-sd", "/data"]
|
||||||
|
volumes:
|
||||||
|
- nats-data:/data
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: openreader
|
||||||
|
POSTGRES_PASSWORD: openreader
|
||||||
|
POSTGRES_DB: openreader
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U openreader -d openreader"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 30
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
compute-worker:
|
||||||
|
image: ghcr.io/richardr1126/openreader-compute-worker:latest
|
||||||
|
depends_on:
|
||||||
|
nats:
|
||||||
|
condition: service_started
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
NATS_URL: nats://nats:4222
|
||||||
|
COMPUTE_WORKER_HOST: 0.0.0.0
|
||||||
|
PORT: 8081
|
||||||
|
COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token}
|
||||||
|
S3_ENDPOINT: http://seaweedfs:8333
|
||||||
|
S3_BUCKET: ${S3_BUCKET:-openreader-documents}
|
||||||
|
S3_REGION: ${S3_REGION:-us-east-1}
|
||||||
|
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret}
|
||||||
|
S3_FORCE_PATH_STYLE: "true"
|
||||||
|
S3_PREFIX: ${S3_PREFIX:-openreader}
|
||||||
|
COMPUTE_PREWARM_MODELS: ${COMPUTE_PREWARM_MODELS:-false}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
openreader-docstore:
|
||||||
|
seaweedfs-data:
|
||||||
|
nats-data:
|
||||||
|
postgres-data:
|
||||||
130
docker/examples/compose.local-full.yml
Normal file
130
docker/examples/compose.local-full.yml
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
name: openreader-local-full-build
|
||||||
|
|
||||||
|
services:
|
||||||
|
openreader:
|
||||||
|
image: openreader:local
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
# Keep localhost:8333 valid for both app requests and browser-facing presigned URLs.
|
||||||
|
network_mode: service:seaweedfs
|
||||||
|
depends_on:
|
||||||
|
kokoro-tts:
|
||||||
|
condition: service_started
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
BASE_URL: ${BASE_URL:-http://localhost:3003}
|
||||||
|
AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me}
|
||||||
|
POSTGRES_URL: postgres://openreader:openreader@postgres:5432/openreader
|
||||||
|
COMPUTE_WORKER_URL: http://compute-worker:8081
|
||||||
|
COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token}
|
||||||
|
USE_EMBEDDED_WEED_MINI: "false"
|
||||||
|
S3_ENDPOINT: ${S3_ENDPOINT:-http://localhost:8333} # Used for internal endpoint and public facing presigned URLs.
|
||||||
|
S3_BUCKET: ${S3_BUCKET:-openreader-documents}
|
||||||
|
S3_REGION: ${S3_REGION:-us-east-1}
|
||||||
|
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret}
|
||||||
|
S3_FORCE_PATH_STYLE: "true"
|
||||||
|
S3_PREFIX: ${S3_PREFIX:-openreader}
|
||||||
|
RUNTIME_SEED_JSON: |-
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"runtimeConfig": {
|
||||||
|
"defaultTtsProvider": "kokoro"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"slug": "kokoro",
|
||||||
|
"displayName": "Kokoro",
|
||||||
|
"providerType": "custom-openai",
|
||||||
|
"baseUrl": "http://kokoro-tts:8880/v1",
|
||||||
|
"defaultModel": "kokoro",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
volumes:
|
||||||
|
- openreader-docstore:/app/docstore
|
||||||
|
|
||||||
|
kokoro-tts:
|
||||||
|
image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
|
||||||
|
environment:
|
||||||
|
ONNX_NUM_THREADS: 8
|
||||||
|
ONNX_INTER_OP_THREADS: 4
|
||||||
|
ONNX_EXECUTION_MODE: parallel
|
||||||
|
ONNX_OPTIMIZATION_LEVEL: all
|
||||||
|
ONNX_MEMORY_PATTERN: "true"
|
||||||
|
ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo
|
||||||
|
API_LOG_LEVEL: DEBUG
|
||||||
|
ports:
|
||||||
|
- "8880:8880"
|
||||||
|
|
||||||
|
seaweedfs:
|
||||||
|
image: chrislusf/seaweedfs:4.18
|
||||||
|
command: ["mini", "-dir=/data"]
|
||||||
|
environment:
|
||||||
|
AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9333/cluster/status >/dev/null"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 30
|
||||||
|
ports:
|
||||||
|
- "3003:3003"
|
||||||
|
- "8333:8333"
|
||||||
|
volumes:
|
||||||
|
- seaweedfs-data:/data
|
||||||
|
|
||||||
|
nats:
|
||||||
|
image: nats:2.14-alpine
|
||||||
|
command: ["-js", "-sd", "/data"]
|
||||||
|
volumes:
|
||||||
|
- nats-data:/data
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: openreader
|
||||||
|
POSTGRES_PASSWORD: openreader
|
||||||
|
POSTGRES_DB: openreader
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U openreader -d openreader"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 30
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
compute-worker:
|
||||||
|
image: openreader-compute-worker:local
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: packages/compute-worker/Dockerfile
|
||||||
|
depends_on:
|
||||||
|
nats:
|
||||||
|
condition: service_started
|
||||||
|
seaweedfs:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
NATS_URL: nats://nats:4222
|
||||||
|
COMPUTE_WORKER_HOST: 0.0.0.0
|
||||||
|
PORT: 8081
|
||||||
|
COMPUTE_WORKER_TOKEN: ${COMPUTE_WORKER_TOKEN:-local-compute-token}
|
||||||
|
S3_ENDPOINT: http://seaweedfs:8333
|
||||||
|
S3_BUCKET: ${S3_BUCKET:-openreader-documents}
|
||||||
|
S3_REGION: ${S3_REGION:-us-east-1}
|
||||||
|
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-devkey}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-devsecret}
|
||||||
|
S3_FORCE_PATH_STYLE: "true"
|
||||||
|
S3_PREFIX: ${S3_PREFIX:-openreader}
|
||||||
|
COMPUTE_PREWARM_MODELS: ${COMPUTE_PREWARM_MODELS:-false}
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
openreader-docstore:
|
||||||
|
seaweedfs-data:
|
||||||
|
nats-data:
|
||||||
|
postgres-data:
|
||||||
52
docker/examples/compose.local-slim.yml
Normal file
52
docker/examples/compose.local-slim.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
name: openreader-local-slim-build
|
||||||
|
|
||||||
|
services:
|
||||||
|
openreader:
|
||||||
|
image: openreader:local
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
depends_on:
|
||||||
|
kokoro-tts:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
BASE_URL: ${BASE_URL:-http://localhost:3003}
|
||||||
|
AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me}
|
||||||
|
RUNTIME_SEED_JSON: |-
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"runtimeConfig": {
|
||||||
|
"defaultTtsProvider": "kokoro"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"slug": "kokoro",
|
||||||
|
"displayName": "Kokoro",
|
||||||
|
"providerType": "custom-openai",
|
||||||
|
"baseUrl": "http://kokoro-tts:8880/v1",
|
||||||
|
"defaultModel": "kokoro",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ports:
|
||||||
|
- "3003:3003"
|
||||||
|
- "8333:8333"
|
||||||
|
volumes:
|
||||||
|
- openreader-docstore:/app/docstore
|
||||||
|
|
||||||
|
kokoro-tts:
|
||||||
|
image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
|
||||||
|
environment:
|
||||||
|
ONNX_NUM_THREADS: 8
|
||||||
|
ONNX_INTER_OP_THREADS: 4
|
||||||
|
ONNX_EXECUTION_MODE: parallel
|
||||||
|
ONNX_OPTIMIZATION_LEVEL: all
|
||||||
|
ONNX_MEMORY_PATTERN: "true"
|
||||||
|
ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo
|
||||||
|
API_LOG_LEVEL: DEBUG
|
||||||
|
ports:
|
||||||
|
- "8880:8880"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
openreader-docstore:
|
||||||
49
docker/examples/compose.yml
Normal file
49
docker/examples/compose.yml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
name: openreader-slim
|
||||||
|
|
||||||
|
services:
|
||||||
|
openreader:
|
||||||
|
image: ghcr.io/richardr1126/openreader:latest
|
||||||
|
depends_on:
|
||||||
|
kokoro-tts:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
BASE_URL: ${BASE_URL:-http://localhost:3003}
|
||||||
|
AUTH_SECRET: ${AUTH_SECRET:-local-openreader-auth-secret-change-me}
|
||||||
|
RUNTIME_SEED_JSON: |-
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"runtimeConfig": {
|
||||||
|
"defaultTtsProvider": "kokoro"
|
||||||
|
},
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"slug": "kokoro",
|
||||||
|
"displayName": "Kokoro",
|
||||||
|
"providerType": "custom-openai",
|
||||||
|
"baseUrl": "http://kokoro-tts:8880/v1",
|
||||||
|
"defaultModel": "kokoro",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ports:
|
||||||
|
- "3003:3003"
|
||||||
|
- "8333:8333"
|
||||||
|
volumes:
|
||||||
|
- openreader-docstore:/app/docstore
|
||||||
|
|
||||||
|
kokoro-tts:
|
||||||
|
image: ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
|
||||||
|
environment:
|
||||||
|
ONNX_NUM_THREADS: 8
|
||||||
|
ONNX_INTER_OP_THREADS: 4
|
||||||
|
ONNX_EXECUTION_MODE: parallel
|
||||||
|
ONNX_OPTIMIZATION_LEVEL: all
|
||||||
|
ONNX_MEMORY_PATTERN: "true"
|
||||||
|
ONNX_ARENA_EXTEND_STRATEGY: kNextPowerOfTwo
|
||||||
|
API_LOG_LEVEL: DEBUG
|
||||||
|
ports:
|
||||||
|
- "8880:8880"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
openreader-docstore:
|
||||||
1
docs-site/.gitignore
vendored
1
docs-site/.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
.docusaurus
|
.docusaurus
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
|
static/changelog
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ This project is built with support from the following open-source projects and t
|
||||||
- [SQLite](https://www.sqlite.org/)
|
- [SQLite](https://www.sqlite.org/)
|
||||||
- [PostgreSQL](https://www.postgresql.org/)
|
- [PostgreSQL](https://www.postgresql.org/)
|
||||||
- [SeaweedFS](https://github.com/seaweedfs/seaweedfs)
|
- [SeaweedFS](https://github.com/seaweedfs/seaweedfs)
|
||||||
- [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
|
- [OpenAI Whisper](https://github.com/openai/whisper)
|
||||||
- [ffmpeg](https://ffmpeg.org)
|
- [ffmpeg](https://ffmpeg.org)
|
||||||
- [react-pdf](https://github.com/wojtekmaj/react-pdf)
|
- [react-pdf](https://github.com/wojtekmaj/react-pdf)
|
||||||
- [react-reader](https://github.com/happyr/react-reader)
|
- [react-reader](https://github.com/happyr/react-reader)
|
||||||
|
|
|
||||||
159
docs-site/docs/configure/admin-panel.md
Normal file
159
docs-site/docs/configure/admin-panel.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -6,22 +6,44 @@ This page covers application-level configuration for provider access and authent
|
||||||
|
|
||||||
## Auth behavior
|
## Auth behavior
|
||||||
|
|
||||||
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
- `BASE_URL` and `AUTH_SECRET` are required at startup in v4+.
|
||||||
- Remove either value to disable auth.
|
|
||||||
- Keep `AUTH_TRUSTED_ORIGINS` empty to trust only `BASE_URL`.
|
- Keep `AUTH_TRUSTED_ORIGINS` empty to trust only `BASE_URL`.
|
||||||
- Anonymous auth sessions are disabled by default.
|
- Anonymous auth sessions are disabled by default.
|
||||||
- Set `USE_ANONYMOUS_AUTH_SESSIONS=true` to enable anonymous session flows.
|
- 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
|
## Route behavior
|
||||||
|
|
||||||
- `/` is a public landing/onboarding page and remains indexable.
|
- `/` is a public landing/onboarding page and remains indexable.
|
||||||
- `/app` is the protected app home (document list and uploader UI).
|
- `/app` is the protected app home (document list and uploader UI).
|
||||||
- If auth is enabled and a valid session exists (including anonymous), visiting `/` redirects to `/app`.
|
- If a valid session exists (including anonymous), visiting `/` redirects to `/app`.
|
||||||
- Protected app routes continue to require auth; when anonymous sessions are disabled and no session exists, users are redirected to `/signin`.
|
- Protected app routes continue to require auth; when anonymous sessions are disabled and no session exists, users are redirected to `/signin`.
|
||||||
|
|
||||||
## Related docs
|
## Related docs
|
||||||
|
|
||||||
- For auth environment variables: [Environment Variables](../reference/environment-variables#auth-and-identity)
|
- 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 TTS character limits and quota behavior: [TTS Rate Limiting](./tts-rate-limiting)
|
||||||
- For provider-specific guidance: [TTS Providers](./tts-providers)
|
- For provider-specific guidance: [TTS Providers](./tts-providers)
|
||||||
- For storage/S3/SeaweedFS behavior: [Object / Blob Storage](./object-blob-storage)
|
- For storage/S3/SeaweedFS behavior: [Object / Blob Storage](./object-blob-storage)
|
||||||
|
|
@ -36,11 +58,7 @@ This page covers application-level configuration for provider access and authent
|
||||||
- Updates are not instant push-based sync; they use normal client polling/refresh behavior.
|
- 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.
|
- If two devices change the same item around the same time, the newest update wins.
|
||||||
|
|
||||||
### Auth disabled
|
|
||||||
|
|
||||||
- Settings and reading progress stay local in the browser (Dexie/IndexedDB).
|
|
||||||
- This avoids no-auth cross-browser conflicts, but there is no cross-device sync.
|
|
||||||
|
|
||||||
## Claim modal note
|
## Claim modal note
|
||||||
|
|
||||||
- You may still see old anonymous settings/progress available to claim from older deployments.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ title: Database
|
||||||
|
|
||||||
This page covers database mode selection for OpenReader.
|
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
|
## Database mode
|
||||||
|
|
||||||
- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
|
- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
|
||||||
|
|
@ -17,9 +22,24 @@ This page covers database mode selection for OpenReader.
|
||||||
- User settings preferences (`user_preferences`) when auth is enabled.
|
- User settings preferences (`user_preferences`) when auth is enabled.
|
||||||
- User reading progress (`user_document_progress`) 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.
|
- 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.
|
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
|
## Related variables
|
||||||
|
|
||||||
- `POSTGRES_URL`
|
- `POSTGRES_URL`
|
||||||
|
|
@ -34,6 +54,5 @@ For database variable behavior, see [Environment Variables](../reference/environ
|
||||||
|
|
||||||
## State sync summary
|
## State sync summary
|
||||||
|
|
||||||
- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
|
- Settings and reading progress are stored in SQL and synced from the app.
|
||||||
- With auth disabled, settings and reading progress remain local in the browser.
|
|
||||||
- Sync is currently request-based (not realtime push invalidation).
|
- Sync is currently request-based (not realtime push invalidation).
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,18 @@ import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
This page covers migration behavior for both database schema and storage data in OpenReader.
|
This page covers migration behavior for both database schema and storage data in OpenReader.
|
||||||
|
|
||||||
|
## Runtime ownership
|
||||||
|
|
||||||
|
- `@openreader/database` owns database clients, schemas, SQL migration files, and programmatic
|
||||||
|
migration execution for SQLite and PostgreSQL.
|
||||||
|
- `@openreader/bootstrap` owns startup orchestration, storage migration, and optional embedded
|
||||||
|
SeaweedFS, NATS, and compute-worker processes.
|
||||||
|
- The Next.js app imports `@openreader/database` directly, but does not orchestrate migrations or
|
||||||
|
child processes.
|
||||||
|
|
||||||
|
Docker deploys bootstrap as an isolated runtime bundle under `/opt/openreader/bootstrap`; it does
|
||||||
|
not merge migration dependencies into the standalone Next.js app under `/app`.
|
||||||
|
|
||||||
## Startup migration behavior
|
## Startup migration behavior
|
||||||
|
|
||||||
By default, the shared entrypoint runs migrations automatically before app startup in:
|
By default, the shared entrypoint runs migrations automatically before app startup in:
|
||||||
|
|
@ -24,6 +36,17 @@ Startup migration phases:
|
||||||
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`
|
||||||
|
|
@ -42,11 +65,6 @@ In most cases, you do not need manual migration commands because startup runs mi
|
||||||
- Postgres when `POSTGRES_URL` is set
|
- Postgres when `POSTGRES_URL` is set
|
||||||
- SQLite when `POSTGRES_URL` is unset
|
- SQLite when `POSTGRES_URL` is unset
|
||||||
|
|
||||||
You can always override the target explicitly with `--config`.
|
|
||||||
|
|
||||||
<Tabs groupId="apply-migration-commands">
|
|
||||||
<TabItem value="project-scripts" label="Project Scripts" default>
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run pending migrations for one target:
|
# Run pending migrations for one target:
|
||||||
# - Postgres if POSTGRES_URL is set
|
# - Postgres if POSTGRES_URL is set
|
||||||
|
|
@ -60,26 +78,15 @@ pnpm migrate-fs
|
||||||
pnpm migrate-fs:dry-run
|
pnpm migrate-fs:dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
`pnpm migrate` uses the programmatic Drizzle migrator from `@openreader/database`. Drizzle Kit is
|
||||||
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
not a production or startup dependency; it is used only to generate new migration files.
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
## Generate migrations
|
## Generate migrations
|
||||||
|
|
||||||
`pnpm generate` is a two-phase script for contributors and schema changes:
|
`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`).
|
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).
|
2. **Drizzle migration generation** — runs `drizzle-kit generate` for both configs in `packages/database`, producing SQL migration files from all schema files (app + auth).
|
||||||
|
|
||||||
:::note
|
:::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.
|
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.
|
||||||
|
|
@ -89,15 +96,23 @@ Most users do not need to run `pnpm generate`. Use it when contributing or when
|
||||||
|
|
||||||
Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
|
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`
|
- `packages/database/src/schema_auth_sqlite.ts`
|
||||||
- `src/db/schema_auth_postgres.ts`
|
- `packages/database/src/schema_auth_postgres.ts`
|
||||||
|
|
||||||
App-specific tables are manually maintained in the standard Drizzle schema files:
|
App-specific tables are manually maintained in the standard Drizzle schema files:
|
||||||
|
|
||||||
- `src/db/schema_sqlite.ts`
|
- `packages/database/src/schema_sqlite.ts`
|
||||||
- `src/db/schema_postgres.ts`
|
- `packages/database/src/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.
|
Both sets of schema files are included in the Drizzle generation configs. Runtime migration
|
||||||
|
execution is owned by `@openreader/database`.
|
||||||
|
|
||||||
|
When app schema changes (for example `tts_segments`), keep these in sync:
|
||||||
|
|
||||||
|
- `packages/database/src/schema_sqlite.ts`
|
||||||
|
- `packages/database/src/schema_postgres.ts`
|
||||||
|
- `packages/database/migrations/sqlite/*.sql` + `packages/database/migrations/sqlite/meta/_journal.json`
|
||||||
|
- `packages/database/migrations/postgres/*.sql` + `packages/database/migrations/postgres/meta/_journal.json`
|
||||||
|
|
||||||
<Tabs groupId="generate-migration-commands">
|
<Tabs groupId="generate-migration-commands">
|
||||||
<TabItem value="project-script" label="Project Script" default>
|
<TabItem value="project-script" label="Project Script" default>
|
||||||
|
|
@ -112,10 +127,10 @@ pnpm generate
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate SQLite migrations only (skips Better Auth CLI)
|
# Generate SQLite migrations only (skips Better Auth CLI)
|
||||||
pnpm exec drizzle-kit generate --config drizzle.config.sqlite.ts
|
pnpm exec drizzle-kit generate --config packages/database/drizzle.config.sqlite.ts
|
||||||
|
|
||||||
# Generate Postgres migrations only (skips Better Auth CLI)
|
# Generate Postgres migrations only (skips Better Auth CLI)
|
||||||
pnpm exec drizzle-kit generate --config drizzle.config.pg.ts
|
pnpm exec drizzle-kit generate --config packages/database/drizzle.config.pg.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,22 @@ import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
This page documents storage backends, blob upload routing, and core Docker mount behavior.
|
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
|
## Storage backends
|
||||||
|
|
||||||
- Embedded (default): SQLite metadata + embedded SeaweedFS (`weed mini`) blobs.
|
- Embedded (default): embedded SeaweedFS (`weed mini`) blob storage.
|
||||||
- External: Postgres + external S3-compatible object 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).
|
Storage variables are documented in [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
|
||||||
|
|
||||||
|
|
@ -101,3 +113,55 @@ Embedded default example: `S3_ENDPOINT=http://127.0.0.1:8333` (or your mapped ho
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
## 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/<url-encoded-user-id>/docs/<document-id>/<document-version>/<settings-hash>/<segment-id>.mp3`
|
||||||
|
- `${S3_PREFIX}/tts_segments_v1/ns/<test-namespace>/users/<url-encoded-user-id>/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.
|
||||||
|
|
||||||
|
<Tabs groupId="tts-segment-storage-access-cli">
|
||||||
|
<TabItem value="aws-s3" label="AWS S3" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all TTS segment objects
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive
|
||||||
|
|
||||||
|
# Filter to one document id (replace <document-id>)
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive | grep "/docs/<document-id>/"
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="s3-compatible" label="Embedded / MinIO / R2 / etc">
|
||||||
|
|
||||||
|
```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 <document-id>)
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/tts_segments_v1/" --recursive --endpoint-url "$S3_ENDPOINT" | grep "/docs/<document-id>/"
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,28 @@ Use DeepInfra's hosted TTS models as your provider.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
**Environment variables (recommended for deployment):**
|
**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
|
```env
|
||||||
API_BASE=https://api.deepinfra.com/v1/openai
|
API_BASE=https://api.deepinfra.com/v1/openai
|
||||||
API_KEY=your-deepinfra-key
|
API_KEY=your-deepinfra-key
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Or in-app via Settings → TTS Provider:**
|
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
|
||||||
|
|
||||||
1. Set provider to `Deepinfra`.
|
1. Set provider to `Deepinfra`.
|
||||||
2. The base URL is pre-filled, no changes needed.
|
2. The base URL is pre-filled, no changes needed.
|
||||||
3. Enter your `API_KEY`.
|
3. Enter your `API_KEY`.
|
||||||
4. Choose a model and voice.
|
4. Choose a model and voice.
|
||||||
|
|
||||||
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,14 @@ docker run -it --rm \
|
||||||
|
|
||||||
## Connect to OpenReader
|
## Connect to OpenReader
|
||||||
|
|
||||||
**Environment variables (recommended for deployment):**
|
**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
|
```env
|
||||||
API_BASE=http://kittentts-fastapi:8005/v1
|
API_BASE=http://kittentts-fastapi:8005/v1
|
||||||
|
|
@ -31,7 +38,7 @@ API_BASE=http://kittentts-fastapi:8005/v1
|
||||||
3. Leave `API_KEY` blank unless your deployment requires one.
|
3. Leave `API_KEY` blank unless your deployment requires one.
|
||||||
4. Choose model `kitten-tts` (or the model your deployment exposes).
|
4. Choose model `kitten-tts` (or the model your deployment exposes).
|
||||||
|
|
||||||
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,14 @@ docker run --name kokoro-tts \
|
||||||
|
|
||||||
## Connect to OpenReader
|
## Connect to OpenReader
|
||||||
|
|
||||||
**Environment variables (recommended for deployment):**
|
**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
|
```env
|
||||||
API_BASE=http://kokoro-tts:8880/v1
|
API_BASE=http://kokoro-tts:8880/v1
|
||||||
|
|
@ -59,7 +66,7 @@ API_BASE=http://kokoro-tts:8880/v1
|
||||||
3. Leave `API_KEY` blank unless your deployment requires one.
|
3. Leave `API_KEY` blank unless your deployment requires one.
|
||||||
4. Choose model `Kokoro`.
|
4. Choose model `Kokoro`.
|
||||||
|
|
||||||
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,28 @@ Use the OpenAI TTS API as your provider.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
**Environment variables (recommended for deployment):**
|
**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
|
```env
|
||||||
API_BASE=https://api.openai.com/v1
|
API_BASE=https://api.openai.com/v1
|
||||||
API_KEY=sk-...
|
API_KEY=sk-...
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=openai
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Or in-app via Settings → TTS Provider:**
|
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
|
||||||
|
|
||||||
1. Set provider to `OpenAI`.
|
1. Set provider to `OpenAI`.
|
||||||
2. The base URL is pre-filled, no changes needed.
|
2. The base URL is pre-filled, no changes needed.
|
||||||
3. Enter your `API_KEY`.
|
3. Enter your `API_KEY`.
|
||||||
4. Choose a model and voice.
|
4. Choose a model and voice.
|
||||||
|
|
||||||
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,14 @@ Refer to the upstream repository for Docker instructions: [Lex-au/Orpheus-FastAP
|
||||||
|
|
||||||
## Connect to OpenReader
|
## Connect to OpenReader
|
||||||
|
|
||||||
**Environment variables (recommended for deployment):**
|
**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
|
```env
|
||||||
API_BASE=http://orpheus:8000/v1
|
API_BASE=http://orpheus:8000/v1
|
||||||
|
|
@ -25,7 +32,7 @@ API_BASE=http://orpheus:8000/v1
|
||||||
3. Leave `API_KEY` blank unless your deployment requires one.
|
3. Leave `API_KEY` blank unless your deployment requires one.
|
||||||
4. Choose model `Orpheus` (or the model your deployment exposes).
|
4. Choose model `Orpheus` (or the model your deployment exposes).
|
||||||
|
|
||||||
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,29 @@ Use any OpenAI-compatible TTS service with OpenReader, including self-hosted ser
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Your service must expose these endpoints:
|
Your service only needs an OpenAI-compatible speech endpoint:
|
||||||
|
|
||||||
- `GET /v1/audio/voices`
|
- `POST /v1/audio/speech` — **required**.
|
||||||
- `POST /v1/audio/speech`
|
- Voice listing is **optional** and auto-discovered from `/v1/audio/voices`, `/v1/voices`, or `/v1/styles`. If none respond, OpenReader falls back to default voices — the Kokoro voice set for Kokoro models, otherwise the standard OpenAI voices (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`).
|
||||||
|
|
||||||
Known compatible implementations: [Kokoro-FastAPI](./kokoro-fastapi), [KittenTTS-FastAPI](./kitten-tts-fastapi), [Orpheus-FastAPI](./orpheus-fastapi).
|
The endpoint may return `mp3`, `wav`, `ogg`, or `flac` — OpenReader normalizes non-mp3 audio to mp3 automatically. An API key is optional.
|
||||||
|
|
||||||
|
Known compatible implementations: [Kokoro-FastAPI](./kokoro-fastapi), [KittenTTS-FastAPI](./kitten-tts-fastapi), [Orpheus-FastAPI](./orpheus-fastapi), [Supertonic](./supertonic).
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
**Environment variables (recommended for deployment):**
|
**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
|
```env
|
||||||
API_BASE=http://your-tts-server/v1
|
API_BASE=http://your-tts-server/v1
|
||||||
API_KEY=optional-key-if-required
|
# API_KEY=optional-key-if-required
|
||||||
```
|
```
|
||||||
|
|
||||||
**Or in-app via Settings → TTS Provider:**
|
**Or in-app via Settings → TTS Provider:**
|
||||||
|
|
@ -29,7 +38,7 @@ API_KEY=optional-key-if-required
|
||||||
3. Set `API_KEY` if your service requires authentication.
|
3. Set `API_KEY` if your service requires authentication.
|
||||||
4. Choose a model and voice supported by your backend.
|
4. Choose a model and voice supported by your backend.
|
||||||
|
|
||||||
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
:::warning TTS requests are server-side
|
:::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`.
|
`API_BASE` must be reachable from the **Next.js server**, not just the browser. In Docker, use container names or `host.docker.internal`.
|
||||||
|
|
@ -37,7 +46,7 @@ Settings modal values override env vars. See [TTS Providers](../tts-providers) f
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
If voices don't load, check that `/v1/audio/voices` is reachable from the server and returns a valid response shape.
|
If voices don't load, confirm the server is reachable from the Next.js runtime and that at least one of `/v1/audio/voices`, `/v1/voices`, or `/v1/styles` returns a valid response. If none do, OpenReader falls back to default voices — synthesis still works as long as `POST /v1/audio/speech` succeeds.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|
|
||||||
48
docs-site/docs/configure/tts-provider-guides/replicate.md
Normal file
48
docs-site/docs/configure/tts-provider-guides/replicate.md
Normal file
|
|
@ -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)
|
||||||
63
docs-site/docs/configure/tts-provider-guides/speech-sdk.md
Normal file
63
docs-site/docs/configure/tts-provider-guides/speech-sdk.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
title: Speech SDK
|
||||||
|
---
|
||||||
|
|
||||||
|
Use [speech-sdk](https://github.com/Jellypod-Inc/speech-sdk) (Apache 2.0) to reach additional cloud TTS providers (ElevenLabs, Cartesia, Hume, Deepgram, Google Gemini TTS, Inworld, and more) with your own provider API keys. Requests go from the OpenReader server directly to the provider's API; no extra account or proxy is involved.
|
||||||
|
|
||||||
|
Models use the `provider/model` format. The API key you enter belongs to the provider named by the model prefix: for `elevenlabs/eleven_multilingual_v2` enter an ElevenLabs key, for `cartesia/sonic-3.5` a Cartesia key, and so on.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||||
|
|
||||||
|
1. Add a shared provider with type `speech-sdk`.
|
||||||
|
2. Enter the API key for the provider you want to use.
|
||||||
|
3. Set default model to a matching `provider/model` (for example `elevenlabs/eleven_multilingual_v2`).
|
||||||
|
|
||||||
|
**Per-user Settings → TTS Provider (only when `restrictUserApiKeys=false`):**
|
||||||
|
|
||||||
|
1. Set provider to `Speech SDK`.
|
||||||
|
2. Choose a model; enter the API key for that model's provider.
|
||||||
|
3. Choose a voice.
|
||||||
|
|
||||||
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
|
## Built-in models
|
||||||
|
|
||||||
|
- `openai/gpt-4o-mini-tts` (works with your existing OpenAI API key)
|
||||||
|
- `elevenlabs/eleven_multilingual_v2`
|
||||||
|
- `cartesia/sonic-3.5`
|
||||||
|
- `deepgram/aura-2`
|
||||||
|
- `google/gemini-2.5-flash-preview-tts`
|
||||||
|
- `inworld/inworld-tts-1.5-max`
|
||||||
|
|
||||||
|
You can also choose `Other` and enter any `provider/model` the SDK supports. Recognized prefixes: `openai`, `elevenlabs`, `cartesia`, `hume`, `deepgram`, `google`, `inworld`, `minimax`, `fish-audio`, `murf`, `resemble`, `fal-ai`, `mistral`, `xai`, `smallest-ai`.
|
||||||
|
|
||||||
|
## Voice IDs
|
||||||
|
|
||||||
|
ElevenLabs and Cartesia identify voices by opaque IDs. The built-in lists map to these shared library voices:
|
||||||
|
|
||||||
|
| ElevenLabs ID | Name | | Cartesia ID | Name |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `JBFqnCBsd6RMkjVDRZzb` | George | | `a0e99841-438c-4a64-b679-ae501e7d6091` | Barbershop Man |
|
||||||
|
| `IKne3meq5aSn9XLyUdCD` | Charlie | | `156fb8d2-335b-4950-9cb3-a2d33f0c0c2a` | British Lady |
|
||||||
|
| `XB0fDUnXU5powFXDhCwa` | Charlotte | | `694f9389-aac1-45b6-b726-9d9369183238` | California Girl |
|
||||||
|
| `Xb7hH8MSUJpSbSDYk0k2` | Alice | | `87748186-23bb-4571-8b8b-a73da9bf9c4f` | Commercial Lady |
|
||||||
|
| `iP95p4xoKVk53GoZ742B` | Chris | | `ee7ea9f8-c0c1-498c-9f62-dc2da49a6f98` | Friendly Reading Man |
|
||||||
|
| `nPczCjzI2devNBz1zQrb` | Brian | | `248be419-c632-4f23-adf1-5324ed7dbf1d` | Hannah |
|
||||||
|
| `onwK4e9ZLuTAKqWW03F9` | Daniel | | | |
|
||||||
|
| `pFZP5JQG7iQjIQuC4Bku` | Lily | | | |
|
||||||
|
| `pqHfZKP75CvOlQylNhV4` | Bill | | | |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- One voice per request; Kokoro-style multi-voice mixing does not apply to this provider.
|
||||||
|
- Playback speed is applied client-side, so cached audio segments stay valid when you change speed.
|
||||||
|
- Providers without a built-in voice list fall back to a `default` entry, which lets the provider pick its default voice.
|
||||||
|
- Word-by-word highlighting works the same as with every other provider (alignment runs in OpenReader, not the provider).
|
||||||
|
- TTS requests are sent from the server, not the browser. The API key is never exposed to clients.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [speech-sdk on GitHub](https://github.com/Jellypod-Inc/speech-sdk)
|
||||||
|
- [TTS Providers](../tts-providers)
|
||||||
68
docs-site/docs/configure/tts-provider-guides/supertonic.md
Normal file
68
docs-site/docs/configure/tts-provider-guides/supertonic.md
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
title: Supertonic
|
||||||
|
---
|
||||||
|
|
||||||
|
Run [Supertonic](https://github.com/supertone-inc/supertonic-py) locally and connect it to OpenReader using the `Custom OpenAI-Like` provider. Supertonic is a fast, on-device TTS engine that ships its own OpenAI-compatible HTTP server.
|
||||||
|
|
||||||
|
:::note No Docker image
|
||||||
|
Supertonic does not publish a Docker image — it installs as a Python package and runs as a local HTTP server. These instructions assume OpenReader itself runs in Docker (the common case); see [Running OpenReader directly on the host](#running-openreader-directly-on-the-host) if you don't.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Run Supertonic
|
||||||
|
|
||||||
|
Install with `pip` (or `pipx` for an isolated install) and start the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install 'supertonic[serve]' # or: pip install 'supertonic[serve]'
|
||||||
|
supertonic serve # defaults; loopback only
|
||||||
|
```
|
||||||
|
|
||||||
|
The first run downloads the model (~400MB). Once it's up, the OpenAI-compatible endpoint is at `http://127.0.0.1:7788/v1/audio/speech` and interactive docs are at `http://127.0.0.1:7788/docs`.
|
||||||
|
|
||||||
|
- **Models:** `supertonic-3` (default) or `supertonic-2`.
|
||||||
|
- **Voices:** built-ins `M1`–`M5` and `F1`–`F5`, plus any custom voices you import. OpenReader discovers them automatically via the `/v1/styles` endpoint.
|
||||||
|
- **Audio format:** Supertonic emits `wav` by default; OpenReader transcodes it to mp3 transparently, so no extra configuration is needed.
|
||||||
|
|
||||||
|
## Connect to OpenReader
|
||||||
|
|
||||||
|
From a Docker container, your host machine is reachable at `host.docker.internal`, so the base URL is `http://host.docker.internal:7788/v1`. On Docker Desktop (macOS/Windows) this reaches the loopback-bound server above with no extra setup.
|
||||||
|
|
||||||
|
:::note Linux (native Docker Engine)
|
||||||
|
On native Linux Docker, `host.docker.internal` needs `--add-host=host.docker.internal:host-gateway` on the OpenReader container (or the equivalent `extra_hosts` entry in `docker-compose.yml`), and it routes to the host's bridge interface rather than loopback. Pick one:
|
||||||
|
|
||||||
|
- Run the OpenReader container with `--network host` (or `network_mode: host`), keep Supertonic on `--host 127.0.0.1`, and use `http://127.0.0.1:7788/v1` as the base URL.
|
||||||
|
- Or start Supertonic with `--host 0.0.0.0` so the bridge can reach it — keep it on a trusted network or behind a firewall.
|
||||||
|
:::
|
||||||
|
|
||||||
|
**Recommended (auth + admin): Settings → Admin → Shared providers**
|
||||||
|
|
||||||
|
1. Add a shared provider with type `custom-openai`.
|
||||||
|
2. Set base URL to `http://host.docker.internal:7788/v1`.
|
||||||
|
3. Leave API key blank — `supertonic serve` does not require one.
|
||||||
|
4. Set default model to `supertonic-3` (or `supertonic-2`).
|
||||||
|
|
||||||
|
**Legacy bootstrap seed (optional, first boot only):**
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=http://host.docker.internal:7788/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or in-app via Settings → TTS Provider:**
|
||||||
|
|
||||||
|
1. Set provider to `Custom OpenAI-Like`.
|
||||||
|
2. Set `API_BASE` to `http://host.docker.internal:7788/v1`.
|
||||||
|
3. Leave `API_KEY` blank.
|
||||||
|
4. Choose model `supertonic-3` (or the model your deployment exposes).
|
||||||
|
|
||||||
|
See [TTS Providers](../tts-providers) for admin-shared vs per-user behavior.
|
||||||
|
|
||||||
|
## Running OpenReader directly on the host
|
||||||
|
|
||||||
|
If OpenReader runs on the same machine (e.g. `pnpm dev`) rather than in Docker, skip `host.docker.internal` and use `http://127.0.0.1:7788/v1` as the base URL everywhere above.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [supertone-inc/supertonic-py](https://github.com/supertone-inc/supertonic-py)
|
||||||
|
- [Supported Languages](https://github.com/supertone-inc/supertonic-py#supported-languages)
|
||||||
|
- [TTS Providers](../tts-providers)
|
||||||
|
- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
|
||||||
|
|
@ -2,30 +2,43 @@
|
||||||
title: TTS Providers
|
title: TTS Providers
|
||||||
---
|
---
|
||||||
|
|
||||||
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. You choose your provider and credentials in one of two places:
|
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. There are three places provider configuration can live:
|
||||||
|
|
||||||
**Environment variables**: set in your `.env` or `docker-compose.yml` as server-level defaults. Applied when the user has no saved Settings.
|
**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).
|
||||||
|
|
||||||
**Settings modal** (Settings > TTS Provider): stored in the browser and sent with every TTS request. **Overrides env vars.**
|
**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`.
|
||||||
|
|
||||||
:::note
|
**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.
|
||||||
Set env vars as deployment-level defaults. Users (or you, in a single-user setup) can then change the provider, base URL, and API key from the Settings modal without redeploying. Clearing the Settings fields falls back to the env var defaults.
|
|
||||||
|
:::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
|
## Providers
|
||||||
|
|
||||||
- **OpenAI**: Cloud. Base URL pre-filled (`https://api.openai.com/v1`). API key required.
|
- **OpenAI**: Cloud. Base URL pre-filled (`https://api.openai.com/v1`). API key required.
|
||||||
- **Deepinfra**: Cloud. Base URL pre-filled (`https://api.deepinfra.com/v1/openai`). 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.
|
||||||
|
- **Speech SDK**: Cloud. Reaches additional providers (ElevenLabs, Cartesia, Hume, Deepgram, Google, Inworld, and more) directly with your own provider API keys via [speech-sdk](./tts-provider-guides/speech-sdk). No base URL. API key required (the key for the model's provider).
|
||||||
- **Custom OpenAI-Like**: Self-hosted or any custom endpoint. `API_BASE` must be set manually (typically ending in `/v1`). API key optional.
|
- **Custom OpenAI-Like**: Self-hosted or any custom endpoint. `API_BASE` must be set manually (typically ending in `/v1`). API key optional.
|
||||||
|
|
||||||
For `OpenAI` and `Deepinfra` you only need to supply an API key. For `Custom OpenAI-Like` you must also set `API_BASE`.
|
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)
|
||||||
|
- **Speech SDK** models: `openai/gpt-4o-mini-tts`, `elevenlabs/eleven_multilingual_v2`, `cartesia/sonic-3.5`, `deepgram/aura-2`, `google/gemini-2.5-flash-preview-tts`, `inworld/inworld-tts-1.5-max` (or choose `Other` and enter any `provider/model` the SDK supports)
|
||||||
|
|
||||||
## Custom provider requirements
|
## Custom provider requirements
|
||||||
|
|
||||||
Self-hosted or custom providers must expose OpenAI-compatible audio endpoints:
|
Self-hosted or custom providers only need an OpenAI-compatible speech endpoint:
|
||||||
|
|
||||||
- `GET /v1/audio/voices`
|
- `POST /v1/audio/speech` — **required**.
|
||||||
- `POST /v1/audio/speech`
|
- Voice listing is **optional** and auto-discovered: OpenReader probes `/v1/audio/voices`, `/v1/voices`, then `/v1/styles`. If none respond, it falls back to default voices — the Kokoro set for Kokoro models, otherwise the standard OpenAI voices (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`).
|
||||||
|
|
||||||
|
The speech endpoint may return any common audio format — `mp3`, `wav`, `ogg`, or `flac`. OpenReader detects the format and transcodes non-mp3 audio to mp3 automatically, so your server does not need to honor `response_format: mp3`. An API key is optional; keyless servers work.
|
||||||
|
|
||||||
:::warning TTS requests are server-side
|
:::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`.
|
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`.
|
||||||
|
|
@ -36,11 +49,15 @@ TTS requests originate from the **Next.js server**, not the browser. `API_BASE`
|
||||||
- [Kokoro-FastAPI](./tts-provider-guides/kokoro-fastapi)
|
- [Kokoro-FastAPI](./tts-provider-guides/kokoro-fastapi)
|
||||||
- [KittenTTS-FastAPI](./tts-provider-guides/kitten-tts-fastapi)
|
- [KittenTTS-FastAPI](./tts-provider-guides/kitten-tts-fastapi)
|
||||||
- [Orpheus-FastAPI](./tts-provider-guides/orpheus-fastapi)
|
- [Orpheus-FastAPI](./tts-provider-guides/orpheus-fastapi)
|
||||||
|
- [Supertonic](./tts-provider-guides/supertonic)
|
||||||
|
- [Replicate](./tts-provider-guides/replicate)
|
||||||
- [DeepInfra](./tts-provider-guides/deepinfra)
|
- [DeepInfra](./tts-provider-guides/deepinfra)
|
||||||
- [OpenAI](./tts-provider-guides/openai)
|
- [OpenAI](./tts-provider-guides/openai)
|
||||||
|
- [Speech SDK](./tts-provider-guides/speech-sdk)
|
||||||
- [Other](./tts-provider-guides/other)
|
- [Other](./tts-provider-guides/other)
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
- [Admin Panel](./admin-panel) — DB-backed shared providers with encrypted keys
|
||||||
- [TTS Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
- [TTS Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||||
- [TTS Rate Limiting](./tts-rate-limiting)
|
- [TTS Rate Limiting](./tts-rate-limiting)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ This page explains OpenReader's TTS character rate limiting controls.
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
- TTS rate limiting is disabled by default.
|
- TTS rate limiting is disabled by default.
|
||||||
- To enable it, set `TTS_ENABLE_RATE_LIMIT=true`.
|
- Primary control is **Settings → Admin → Site features → Disable TTS daily rate limiting**.
|
||||||
- Limits are enforced per day in UTC.
|
- Limits are enforced per day in UTC.
|
||||||
- Enforcement applies only when auth is enabled.
|
- Enforcement applies only when auth is enabled.
|
||||||
|
|
||||||
|
|
@ -28,24 +28,15 @@ If a request exceeds the active limit, the TTS API returns `429` with reset meta
|
||||||
- `DISABLE_AUTH_RATE_LIMIT` only affects Better Auth's own request throttling.
|
- `DISABLE_AUTH_RATE_LIMIT` only affects Better Auth's own request throttling.
|
||||||
- `DISABLE_AUTH_RATE_LIMIT` does not disable TTS character limits.
|
- `DISABLE_AUTH_RATE_LIMIT` does not disable TTS character limits.
|
||||||
|
|
||||||
## Environment variables
|
## Runtime config
|
||||||
|
|
||||||
Enable/disable:
|
- `disableTtsRateLimit` default: `true`
|
||||||
|
- Per-user and IP backstop limit values are configured in **Settings → Admin → Site features** and stored in DB runtime settings.
|
||||||
- `TTS_ENABLE_RATE_LIMIT` (default: `false`)
|
- Optional first-boot seeding can be done via `RUNTIME_SEED_JSON` / `RUNTIME_SEED_JSON_PATH` (`runtimeConfig.disableTtsRateLimit`).
|
||||||
|
|
||||||
Per-user daily limits:
|
|
||||||
|
|
||||||
- `TTS_DAILY_LIMIT_ANONYMOUS` (default: `50000`)
|
|
||||||
- `TTS_DAILY_LIMIT_AUTHENTICATED` (default: `500000`)
|
|
||||||
|
|
||||||
IP backstop daily limits:
|
|
||||||
|
|
||||||
- `TTS_IP_DAILY_LIMIT_ANONYMOUS` (default: `100000`)
|
|
||||||
- `TTS_IP_DAILY_LIMIT_AUTHENTICATED` (default: `1000000`)
|
|
||||||
|
|
||||||
## Related docs
|
## Related docs
|
||||||
|
|
||||||
- TTS/rate-limit environment variables: [Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
- 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)
|
- Auth configuration: [Auth](./auth)
|
||||||
- Provider setup: [TTS Providers](./tts-providers)
|
- Provider setup: [TTS Providers](./tts-providers)
|
||||||
|
|
|
||||||
134
docs-site/docs/deploy/compute-worker.md
Normal file
134
docs-site/docs/deploy/compute-worker.md
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
---
|
||||||
|
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 resource-specific operations under `/v1` and listens for updates on
|
||||||
|
`GET /v1/operations/: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=<same-token-as-worker>
|
||||||
|
# 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=<shared-token>
|
||||||
|
NATS_URL=tls://connect.ngs.global:4222
|
||||||
|
NATS_CREDS="-----BEGIN NATS USER JWT-----
|
||||||
|
...
|
||||||
|
------END USER NKEY SEED------"
|
||||||
|
S3_BUCKET=<bucket>
|
||||||
|
S3_REGION=<region>
|
||||||
|
S3_ACCESS_KEY_ID=<key>
|
||||||
|
S3_SECRET_ACCESS_KEY=<secret>
|
||||||
|
# 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://<railway-worker-domain>
|
||||||
|
COMPUTE_WORKER_TOKEN=<same-token-as-worker>
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the worker after deploy:
|
||||||
|
|
||||||
|
- `GET https://<railway-worker-domain>/health/live`
|
||||||
|
- `GET https://<railway-worker-domain>/health/ready`
|
||||||
175
docs-site/docs/deploy/docker-compose.md
Normal file
175
docs-site/docs/deploy/docker-compose.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
---
|
||||||
|
title: Docker Compose
|
||||||
|
description: Run OpenReader with the slim, full, local-slim, or local-full Docker Compose examples.
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
|
Use these examples to run OpenReader with Kokoro-FastAPI and persistent storage. Choose the slim
|
||||||
|
stack for the simplest deployment, or the full stack when you want PostgreSQL, SeaweedFS, NATS,
|
||||||
|
and the compute worker as separate containers. Local build variants are also available for both slim
|
||||||
|
and full stacks to build the application from your current checkout.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- A recent Docker version with Docker Compose
|
||||||
|
- A clone of the OpenReader repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/richardr1126/openreader.git
|
||||||
|
cd openreader
|
||||||
|
```
|
||||||
|
|
||||||
|
## Choose a stack
|
||||||
|
|
||||||
|
<Tabs groupId="docker-compose-stack">
|
||||||
|
<TabItem value="slim" label="Slim" default>
|
||||||
|
|
||||||
|
The default slim example runs:
|
||||||
|
|
||||||
|
- OpenReader with embedded SeaweedFS, NATS, compute worker, and SQLite
|
||||||
|
- Kokoro-FastAPI as a companion container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker/examples/compose.yml up
|
||||||
|
# Repository convenience command: pnpm compose
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose file: [`docker/examples/compose.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.yml)
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="full" label="Full">
|
||||||
|
|
||||||
|
The full example runs OpenReader, Kokoro-FastAPI, PostgreSQL, SeaweedFS, NATS, and the compute
|
||||||
|
worker as separate containers using published images.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker/examples/compose.full.yml up
|
||||||
|
# Repository convenience command: pnpm compose:full
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose file: [`docker/examples/compose.full.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.full.yml)
|
||||||
|
|
||||||
|
For details about running the worker separately, see
|
||||||
|
[Compute Worker](./compute-worker).
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-slim" label="Local Slim">
|
||||||
|
|
||||||
|
The local-slim example runs a slim setup (OpenReader and Kokoro-FastAPI), but builds the OpenReader app image from the current checkout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker/examples/compose.local-slim.yml up --build
|
||||||
|
# Repository convenience command: pnpm compose:local
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose file: [`docker/examples/compose.local-slim.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.local-slim.yml)
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-full" label="Local Full">
|
||||||
|
|
||||||
|
The local-full example uses the full multi-container layout, but builds the OpenReader app and compute-worker images from the current checkout.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker/examples/compose.local-full.yml up --build
|
||||||
|
# Repository convenience command: pnpm compose:local:full
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose file: [`docker/examples/compose.local-full.yml`](https://github.com/richardr1126/openreader/blob/main/docker/examples/compose.local-full.yml)
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Included services
|
||||||
|
|
||||||
|
| Service | Slim | Full | Local Slim | Local Full |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| OpenReader | Published image | Published image | Local build | Local build |
|
||||||
|
| Kokoro-FastAPI | Container | Container | Container | Container |
|
||||||
|
| Database | Embedded SQLite | PostgreSQL container | Embedded SQLite | PostgreSQL container |
|
||||||
|
| SeaweedFS | Embedded | Container | Embedded | Container |
|
||||||
|
| NATS | Embedded | Container | Embedded | Container |
|
||||||
|
| Compute worker | Embedded | Published image | Embedded | Local build |
|
||||||
|
|
||||||
|
On first boot, `RUNTIME_SEED_JSON` creates an enabled Kokoro shared provider and selects it as the
|
||||||
|
default TTS provider.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
- OpenReader: `http://localhost:3003`
|
||||||
|
- SeaweedFS S3: `http://localhost:8333`
|
||||||
|
- Kokoro-FastAPI: `http://localhost:8880`
|
||||||
|
|
||||||
|
In the full examples, PostgreSQL, the compute worker, and NATS remain internal to the Compose
|
||||||
|
network.
|
||||||
|
|
||||||
|
## LAN access
|
||||||
|
|
||||||
|
Set `BASE_URL` and `S3_ENDPOINT` to the Docker host's LAN IP so browser-facing app and presigned
|
||||||
|
S3 URLs are reachable from other devices:
|
||||||
|
|
||||||
|
<Tabs groupId="docker-compose-lan-stack">
|
||||||
|
<TabItem value="slim" label="Slim" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE_URL=http://192.168.0.XXX:3003 \
|
||||||
|
S3_ENDPOINT=http://192.168.0.XXX:8333 \
|
||||||
|
docker compose -f docker/examples/compose.yml up
|
||||||
|
# Repository convenience command: pnpm compose
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="full" label="Full">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE_URL=http://192.168.0.XXX:3003 \
|
||||||
|
S3_ENDPOINT=http://192.168.0.XXX:8333 \
|
||||||
|
docker compose -f docker/examples/compose.full.yml up
|
||||||
|
# Repository convenience command: pnpm compose:full
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-slim" label="Local Slim">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE_URL=http://192.168.0.XXX:3003 \
|
||||||
|
S3_ENDPOINT=http://192.168.0.XXX:8333 \
|
||||||
|
docker compose -f docker/examples/compose.local-slim.yml up --build
|
||||||
|
# Repository convenience command: pnpm compose:local
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-full" label="Local Full">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE_URL=http://192.168.0.XXX:3003 \
|
||||||
|
S3_ENDPOINT=http://192.168.0.XXX:8333 \
|
||||||
|
docker compose -f docker/examples/compose.local-full.yml up --build
|
||||||
|
# Repository convenience command: pnpm compose:local:full
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Replace `192.168.0.XXX` with your Docker host's LAN IP and allow inbound TCP ports `3003` and
|
||||||
|
`8333` through its firewall.
|
||||||
|
|
||||||
|
:::info Internal full-stack endpoint
|
||||||
|
The full and local-full compute workers continue using `http://seaweedfs:8333` internally.
|
||||||
|
`S3_ENDPOINT` configures the app endpoint and browser-facing presigned URLs.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The examples use local-only default credentials. Override existing `${VARIABLE}` values through
|
||||||
|
your shell environment before using them beyond local development.
|
||||||
|
|
||||||
|
:::warning Protect public deployments
|
||||||
|
Replace the default `AUTH_SECRET`, PostgreSQL credentials, S3 credentials, and compute-worker
|
||||||
|
token before exposing a stack outside your trusted local network.
|
||||||
|
:::
|
||||||
|
|
||||||
|
For the complete configuration reference, see
|
||||||
|
[Environment Variables](../reference/environment-variables). See [Database](../configure/database)
|
||||||
|
for PostgreSQL and SQLite behavior.
|
||||||
|
|
@ -7,58 +7,167 @@ import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Node.js (recommended with [nvm](https://github.com/nvm-sh/nvm))
|
<details>
|
||||||
- `pnpm` (recommended) or `npm`
|
<summary><strong>Node.js + pnpm (required)</strong></summary>
|
||||||
|
|
||||||
|
<Tabs groupId="local-dev-node-pnpm-os">
|
||||||
|
<TabItem value="macos" label="macOS" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g pnpm
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
- A reachable TTS API server
|
</TabItem>
|
||||||
- [SeaweedFS](https://github.com/seaweedfs/seaweedfs) `weed` binary (required unless using external S3 storage)
|
<TabItem value="linux" label="Linux">
|
||||||
|
|
||||||
<Tabs groupId="seaweedfs-install">
|
```bash
|
||||||
<TabItem value="macos" label="macOS" default>
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>SeaweedFS <code>weed</code> binary (required unless using external S3)</strong></summary>
|
||||||
|
|
||||||
|
<Tabs groupId="local-dev-seaweed-os">
|
||||||
|
<TabItem value="macos" label="macOS" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install seaweedfs
|
brew install seaweedfs
|
||||||
|
weed version
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
:::warning SeaweedFS Compatibility Note (April 16, 2026)
|
||||||
<TabItem value="linux" label="Linux">
|
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.
|
||||||
|
:::
|
||||||
|
|
||||||
Install the `weed` binary from the [SeaweedFS releases](https://github.com/seaweedfs/seaweedfs/releases) and ensure it is available on `PATH`.
|
</TabItem>
|
||||||
|
<TabItem value="linux" label="Linux">
|
||||||
|
|
||||||
</TabItem>
|
```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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
Optional, depending on features:
|
</details>
|
||||||
|
|
||||||
- [libreoffice](https://www.libreoffice.org) (required for DOCX conversion)
|
<details>
|
||||||
|
<summary><strong>NATS Server <code>nats-server</code> (required for embedded compute mode)</strong></summary>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<Tabs groupId="local-dev-nats-os">
|
||||||
|
<TabItem value="macos" label="macOS" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install nats-server
|
||||||
|
nats-server -v
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="linux" label="Linux">
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>LibreOffice (optional, for DOCX conversion)</strong></summary>
|
||||||
|
|
||||||
|
<Tabs groupId="local-dev-libreoffice-os">
|
||||||
|
<TabItem value="macos" label="macOS" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install libreoffice
|
brew install libreoffice
|
||||||
```
|
```
|
||||||
|
|
||||||
- [whisper.cpp](https://github.com/ggml-org/whisper.cpp) (optional, for word-by-word highlighting)
|
</TabItem>
|
||||||
|
<TabItem value="linux" label="Linux">
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clone and build whisper.cpp (no model download needed – OpenReader handles that)
|
# Debian/Ubuntu example
|
||||||
git clone https://github.com/ggml-org/whisper.cpp.git
|
sudo apt update
|
||||||
cd whisper.cpp
|
sudo apt install -y libreoffice
|
||||||
cmake -B build
|
|
||||||
cmake --build build -j --config Release
|
|
||||||
|
|
||||||
# point OpenReader to the compiled whisper-cli binary
|
|
||||||
echo WHISPER_CPP_BIN="$(pwd)/build/bin/whisper-cli"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
</TabItem>
|
||||||
Set `WHISPER_CPP_BIN` in your `.env` to enable word-by-word highlighting.
|
</Tabs>
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Word-by-word highlighting (optional)</strong></summary>
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
:::tip Docker Compose
|
||||||
|
To run OpenReader and Kokoro-FastAPI with Docker Compose, including slim, full, and local-build
|
||||||
|
options, see [Docker Compose](./docker-compose).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Steps
|
## Steps
|
||||||
|
|
||||||
|
### Required flow
|
||||||
|
|
||||||
1. Clone the repository.
|
1. Clone the repository.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -80,46 +189,123 @@ cp .env.example .env
|
||||||
|
|
||||||
Then edit `.env`.
|
Then edit `.env`.
|
||||||
|
|
||||||
- No auth mode: leave `BASE_URL` or `AUTH_SECRET` unset.
|
Default embedded worker flow (no external worker URL):
|
||||||
- Auth enabled mode: set both `BASE_URL` (typically `http://localhost:3003`) and `AUTH_SECRET` (generate with `openssl rand -hex 32`).
|
|
||||||
|
|
||||||
Optional:
|
```env
|
||||||
|
# Leave COMPUTE_WORKER_URL unset.
|
||||||
|
# Entry point auto-starts embedded worker+NATS when available.
|
||||||
|
```
|
||||||
|
|
||||||
- `AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://192.168.0.116:3003`
|
External worker flow:
|
||||||
- Stable S3 credentials via `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`
|
|
||||||
- External S3 storage by setting `USE_EMBEDDED_WEED_MINI=false` and related S3 vars
|
```env
|
||||||
|
COMPUTE_WORKER_URL=http://localhost:8081
|
||||||
|
COMPUTE_WORKER_TOKEN=<same-token-used-by-worker>
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<Tabs groupId="local-env-modes">
|
||||||
|
<TabItem value="auth-enabled" label="Auth Enabled" default>
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=http://host.docker.internal:8880/v1
|
||||||
|
BASE_URL=http://localhost:3003
|
||||||
|
AUTH_SECRET=<generate-with-openssl-rand-base64-32>
|
||||||
|
# Optional when you need multiple local origins:
|
||||||
|
# AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="auth-with-admin" label="Auth + Admin Panel">
|
||||||
|
|
||||||
|
```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=<generate-with-openssl-rand-base64-32>
|
||||||
|
# Comma-separated emails to auto-promote to admin on signin.
|
||||||
|
ADMIN_EMAILS=you@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="external-s3" label="External S3">
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=http://host.docker.internal:8880/v1
|
||||||
|
USE_EMBEDDED_WEED_MINI=false
|
||||||
|
BASE_URL=http://localhost:3003
|
||||||
|
AUTH_SECRET=<generate-with-openssl-rand-base64-32>
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="worker-mode" label="External Worker Service">
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=http://host.docker.internal:8880/v1
|
||||||
|
BASE_URL=http://localhost:3003
|
||||||
|
AUTH_SECRET=<generate-with-openssl-rand-base64-32>
|
||||||
|
COMPUTE_WORKER_URL=http://localhost:8081
|
||||||
|
COMPUTE_WORKER_TOKEN=<same-token-used-by-worker>
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
:::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
|
:::info
|
||||||
For all environment variables, see [Environment Variables](../reference/environment-variables).
|
For all environment variables, see [Environment Variables](../reference/environment-variables).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
See [Auth](../configure/auth) for app/auth behavior.
|
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).
|
Storage configuration details are in [Object / Blob Storage](../configure/object-blob-storage).
|
||||||
Refer to [Database](../configure/database) for database modes.
|
Refer to [Database](../configure/database) for database modes.
|
||||||
Learn about migration behavior and commands in [Migrations](../configure/migrations).
|
Learn about migration behavior and commands in [Migrations](../configure/migrations).
|
||||||
|
|
||||||
4. Run DB 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**.
|
||||||
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
|
|
||||||
- You only need manual migration commands for one-off troubleshooting or explicit migration workflows:
|
|
||||||
|
|
||||||
```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`.
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
5. Start the app.
|
4. Start the app.
|
||||||
|
|
||||||
<Tabs groupId="local-run-mode">
|
<Tabs groupId="local-run-mode">
|
||||||
<TabItem value="dev" label="Dev" default>
|
<TabItem value="dev" label="Dev (recommended)" default>
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm dev
|
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.
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem value="prod" label="Build + Start">
|
<TabItem value="prod" label="Build + Start">
|
||||||
|
|
||||||
|
|
@ -136,3 +322,17 @@ pnpm start
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Visit [http://localhost:3003](http://localhost:3003).
|
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`.
|
||||||
|
:::
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ This guide covers deploying OpenReader to Vercel with external Postgres and S3-c
|
||||||
|
|
||||||
- Documents (PDF/EPUB/TXT/MD) work with `POSTGRES_URL` + external S3 storage.
|
- Documents (PDF/EPUB/TXT/MD) work with `POSTGRES_URL` + external S3 storage.
|
||||||
- Audiobook routes work on Node.js serverless functions using `ffmpeg-static`.
|
- 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
|
:::warning DOCX Conversion Limitation
|
||||||
`docx` conversion requires `soffice` (LibreOffice), which is not available in a standard Vercel runtime.
|
`docx` conversion requires `soffice` (LibreOffice), which is not available in a standard Vercel runtime.
|
||||||
|
|
@ -15,11 +17,10 @@ This guide covers deploying OpenReader to Vercel with external Postgres and S3-c
|
||||||
|
|
||||||
## 1. Environment Variables
|
## 1. Environment Variables
|
||||||
|
|
||||||
Recommended production setup (auth enabled):
|
Recommended production setup (auth enabled, admin panel enabled):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
API_BASE=https://api.deepinfra.com/v1/openai
|
# Infrastructure
|
||||||
API_KEY=your_deepinfra_key
|
|
||||||
POSTGRES_URL=postgres://...
|
POSTGRES_URL=postgres://...
|
||||||
USE_EMBEDDED_WEED_MINI=false
|
USE_EMBEDDED_WEED_MINI=false
|
||||||
S3_ACCESS_KEY_ID=...
|
S3_ACCESS_KEY_ID=...
|
||||||
|
|
@ -27,51 +28,104 @@ S3_SECRET_ACCESS_KEY=...
|
||||||
S3_BUCKET=...
|
S3_BUCKET=...
|
||||||
S3_REGION=us-east-1
|
S3_REGION=us-east-1
|
||||||
S3_PREFIX=openreader
|
S3_PREFIX=openreader
|
||||||
BASE_URL=https://your-app.vercel.app
|
|
||||||
AUTH_SECRET=...
|
|
||||||
# Optional client/runtime feature defaults:
|
|
||||||
NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
|
|
||||||
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
|
||||||
NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false
|
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
|
||||||
NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false
|
|
||||||
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
|
||||||
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
|
||||||
# Optional (non-AWS S3-compatible providers):
|
# Optional (non-AWS S3-compatible providers):
|
||||||
# S3_ENDPOINT=https://...
|
# S3_ENDPOINT=https://...
|
||||||
# S3_FORCE_PATH_STYLE=true
|
# 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://<railway-worker-domain>
|
||||||
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
:::info Production Configuration & Feature Flags
|
If you also run an external worker service (for example Railway), set these there too:
|
||||||
We recommend setting these defaults for a production-like environment:
|
|
||||||
|
|
||||||
- `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false`: Disables DOCX upload (requires external tools anyway)
|
- `LOG_FORMAT=json`
|
||||||
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
- `COMPUTE_LOG_LEVEL=info`
|
||||||
- `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false`: Hides the Settings -> TTS Provider section
|
|
||||||
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
:::note Env vars vs. admin panel (important for Vercel)
|
||||||
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
`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).
|
||||||
- `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false`: Restricts usage to free models if no key is provided
|
|
||||||
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
|
||||||
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## 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://<railway-worker-domain>` (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
|
:::warning Auth recommendation
|
||||||
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
|
Set both `BASE_URL` and `AUTH_SECRET` — they are required in v4+ and also required for the admin panel and for encrypting admin-stored TTS credentials.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning Rotating AUTH_SECRET invalidates admin-stored keys
|
||||||
|
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
|
:::tip
|
||||||
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
|
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 2. FFmpeg packaging in Vercel functions
|
## 4. Database and data migrations
|
||||||
|
|
||||||
|
Vercel deployments do not run the `@openreader/bootstrap` process, 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:
|
`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`
|
||||||
- `/api/audiobook/chapter`
|
- `/api/audiobook/chapter`
|
||||||
- `/api/audiobook/status`
|
- `/api/tts/segments/ensure`
|
||||||
- `/api/whisper`
|
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
|
`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
|
||||||
|
|
@ -79,7 +133,7 @@ For all variables and defaults, see [Environment Variables](../reference/environ
|
||||||
|
|
||||||
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
|
If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
|
||||||
|
|
||||||
## 3. Function memory sizing
|
## 7. Function memory sizing
|
||||||
|
|
||||||
FFmpeg workloads benefit from more memory/CPU. This repo includes:
|
FFmpeg workloads benefit from more memory/CPU. This repo includes:
|
||||||
|
|
||||||
|
|
@ -88,23 +142,22 @@ FFmpeg workloads benefit from more memory/CPU. This repo includes:
|
||||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||||
"functions": {
|
"functions": {
|
||||||
"app/api/audiobook/route.ts": { "memory": 3009 },
|
"app/api/audiobook/route.ts": { "memory": 3009 },
|
||||||
"app/api/whisper/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.
|
Adjust memory per route if your files are larger or your plan differs.
|
||||||
|
|
||||||
## 4. Runtime expectations and caveats
|
## 8. Runtime expectations and caveats
|
||||||
|
|
||||||
- Audiobook APIs require S3 configuration; otherwise they return `503`.
|
- Audiobook APIs require S3 configuration; otherwise they return `503`.
|
||||||
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
|
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
|
||||||
- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
|
|
||||||
- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
|
|
||||||
|
|
||||||
## 5. Smoke test after deploy
|
## 9. Smoke test after deploy
|
||||||
|
|
||||||
1. Upload and read a PDF/EPUB document.
|
1. Upload and read a PDF/EPUB document.
|
||||||
2. Confirm sync/blob fetch works across refreshes/devices.
|
2. Confirm sync/blob fetch works across refreshes/devices.
|
||||||
3. Generate at least one audiobook chapter and play/download it.
|
3. Generate at least one audiobook chapter and play/download it.
|
||||||
4. If using word highlighting, verify timestamps are produced and rendered.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,32 @@ import TabItem from '@theme/TabItem';
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- A recent Docker version installed
|
- A recent Docker version installed
|
||||||
- A TTS API server that OpenReader can reach (Kokoro-FastAPI, KittenTTS-FastAPI, Orpheus-FastAPI, DeepInfra, OpenAI, or equivalent)
|
- 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)
|
||||||
|
|
||||||
:::note
|
:::warning SeaweedFS Compatibility Note (April 16, 2026)
|
||||||
If you have suitable hardware, you can run Kokoro locally with Docker. See [Kokoro-FastAPI](./configure/tts-provider-guides/kokoro-fastapi).
|
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
|
## 1. Start the Docker container
|
||||||
|
|
||||||
<Tabs groupId="docker-start-mode">
|
<Tabs groupId="docker-start-mode">
|
||||||
<TabItem value="minimal" label="Minimal" default>
|
<TabItem value="localhost" label="Localhost" default>
|
||||||
|
|
||||||
Auth disabled, embedded storage ephemeral, no library import:
|
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 \
|
|
||||||
ghcr.io/richardr1126/openreader:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="localhost" label="Localhost">
|
|
||||||
|
|
||||||
Persistent storage, embedded SeaweedFS `weed mini`, optional auth, optional library mount:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --name openreader \
|
docker run --name openreader \
|
||||||
|
|
@ -40,16 +41,24 @@ docker run --name openreader \
|
||||||
-p 3003:3003 \
|
-p 3003:3003 \
|
||||||
-p 8333:8333 \
|
-p 8333:8333 \
|
||||||
-v openreader_docstore:/app/docstore \
|
-v openreader_docstore:/app/docstore \
|
||||||
-v /path/to/your/library:/app/docstore/library:ro \
|
|
||||||
-e API_BASE=http://host.docker.internal:8880/v1 \
|
-e API_BASE=http://host.docker.internal:8880/v1 \
|
||||||
-e API_KEY=none \
|
|
||||||
-e BASE_URL=http://localhost:3003 \
|
-e BASE_URL=http://localhost:3003 \
|
||||||
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
-e AUTH_SECRET=$(openssl rand -base64 32) \
|
||||||
|
-e ADMIN_EMAILS=you@example.com \
|
||||||
ghcr.io/richardr1126/openreader:latest
|
ghcr.io/richardr1126/openreader:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
What this command enables:
|
||||||
<TabItem value="local-network" label="LAN Host">
|
|
||||||
|
- `-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.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-network" label="LAN Host">
|
||||||
|
|
||||||
Use this when the app should be reachable from other devices on your LAN:
|
Use this when the app should be reachable from other devices on your LAN:
|
||||||
|
|
||||||
|
|
@ -61,21 +70,59 @@ docker run --name openreader \
|
||||||
-v openreader_docstore:/app/docstore \
|
-v openreader_docstore:/app/docstore \
|
||||||
-e API_BASE=http://host.docker.internal:8880/v1 \
|
-e API_BASE=http://host.docker.internal:8880/v1 \
|
||||||
-e BASE_URL=http://<YOUR_LAN_IP>:3003 \
|
-e BASE_URL=http://<YOUR_LAN_IP>:3003 \
|
||||||
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
-e AUTH_SECRET=$(openssl rand -base64 32) \
|
||||||
-e AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 \
|
-e AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 \
|
||||||
-e USE_ANONYMOUS_AUTH_SESSIONS=true \
|
-e USE_ANONYMOUS_AUTH_SESSIONS=true \
|
||||||
|
-e ADMIN_EMAILS=you@example.com \
|
||||||
ghcr.io/richardr1126/openreader:latest
|
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.
|
Replace `YOUR_LAN_IP` with the Docker host IP address on your local network to allow access from other devices.
|
||||||
|
|
||||||
</TabItem>
|
What this command enables:
|
||||||
|
|
||||||
|
- LAN access from phones/tablets/other computers via `http://<YOUR_LAN_IP>: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.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="minimal" label="Minimal">
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
:::tip Quick Tips
|
:::tip Quick Tips
|
||||||
- Remove `/app/docstore/library` if you do not need server library import.
|
- 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**.
|
||||||
- Remove either `BASE_URL` or `AUTH_SECRET` to keep auth disabled.
|
- `BASE_URL` and `AUTH_SECRET` are required in v4+. The admin panel requires auth.
|
||||||
- Set `API_BASE` to your reachable TTS server base URL.
|
- 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
|
:::warning Port `8333` Exposure
|
||||||
|
|
@ -84,28 +131,18 @@ Expose `8333` for direct browser presigned upload/download with embedded Seaweed
|
||||||
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`.
|
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`.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::info Auth and Migrations
|
|
||||||
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
|
||||||
- DB/storage migrations run automatically at container startup via the shared entrypoint.
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::info Related Docs
|
|
||||||
- [Environment Variables](./reference/environment-variables)
|
|
||||||
- [Auth](./configure/auth)
|
|
||||||
- [Database](./configure/database)
|
|
||||||
- [Object / Blob Storage](./configure/object-blob-storage)
|
|
||||||
- [Migrations](./configure/migrations)
|
|
||||||
:::
|
|
||||||
|
|
||||||
## 2. Configure settings in the app UI
|
## 2. Configure settings in the app UI
|
||||||
|
|
||||||
- Set TTS provider and model in Settings
|
Visit [http://localhost:3003](http://localhost:3003) after startup.
|
||||||
- Set TTS API base URL and API key if needed
|
|
||||||
- Select the model voice from the voice dropdown
|
- 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
|
## 3. Update Docker image
|
||||||
|
|
||||||
Legacy image compatibility: `ghcr.io/richardr1126/openreader-webui:latest` remains available as an alias.
|
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
|
```bash
|
||||||
docker stop openreader || true && \
|
docker stop openreader || true && \
|
||||||
|
|
@ -118,4 +155,11 @@ docker pull ghcr.io/richardr1126/openreader:latest
|
||||||
If you use a mounted volume for `/app/docstore`, your persisted data remains after image updates.
|
If you use a mounted volume for `/app/docstore`, your persisted data remains after image updates.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Visit [http://localhost:3003](http://localhost:3003) after startup.
|
:::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)
|
||||||
|
:::
|
||||||
|
|
|
||||||
|
|
@ -4,35 +4,30 @@ title: Introduction
|
||||||
slug: /
|
slug: /
|
||||||
---
|
---
|
||||||
|
|
||||||
OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
|
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**.
|
> Previously named **OpenReader-WebUI**.
|
||||||
|
|
||||||
It supports multiple TTS providers including OpenAI, 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).
|
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
|
## ✨ 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**
|
- 🎯 **Multi-Provider TTS Support**
|
||||||
- [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI): supports multi-voice combinations (for example `af_heart+af_bella`)
|
- 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
|
||||||
- [**KittenTTS-FastAPI**](https://github.com/richardr1126/KittenTTS-FastAPI): lightweight, CPU-friendly self-hosted TTS
|
- 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)
|
||||||
- [**Orpheus-FastAPI**](https://github.com/Lex-au/Orpheus-FastAPI)
|
- 🌐 **Multilingual Support**
|
||||||
- **Custom OpenAI-compatible**: any TTS API with `/v1/audio/voices` and `/v1/audio/speech` endpoints
|
- Choose a document language for language-aware narration and highlighting
|
||||||
- **Cloud TTS providers**:
|
- Available languages depend on the configured provider, model, and voice
|
||||||
- [**DeepInfra**](https://deepinfra.com/models/text-to-speech): Kokoro-82M and other hosted models
|
- 🎧 **Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
|
||||||
- [**OpenAI API**](https://platform.openai.com/docs/pricing#transcription-and-speech): `tts-1`, `tts-1-hd`, and `gpt-4o-mini-tts`
|
- 🗂️ **Flexible Backend** — embedded SeaweedFS or S3-compatible storage, SQLite or Postgres, server library import, and device sync
|
||||||
- 🛜 **Server-side Document Storage**
|
- 🔐 **Auth and User Isolation** — auth is required in v4+, with optional anonymous auth sessions for guest flows
|
||||||
- Documents are persisted in server blob/object storage for consistent access
|
- 🎨 **Customizable** — 13 built-in themes (light and dark palettes), per-user TTS settings, and document handling controls
|
||||||
- 📚 **External Library Import**
|
|
||||||
- Import documents from server-mounted folders
|
|
||||||
- 🎧 **Server-side Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
|
|
||||||
- 📖 **Read Along Experience**
|
|
||||||
- Real-time highlighting for PDF/EPUB, with optional word-level [whisper.cpp](https://github.com/ggml-org/whisper.cpp) timestamps
|
|
||||||
- 🔐 **Auth Optional by Design**
|
|
||||||
- Run no-auth for local use, or enable auth with user isolation and claim flow
|
|
||||||
- 🗂️ **Flexible Storage and Database Modes** with embedded defaults or external S3/Postgres
|
|
||||||
- 🚀 **Production-ready Server Behavior** with TTS caching/retries/rate limits and startup migrations
|
|
||||||
- 🎨 **Customizable Experience**
|
|
||||||
- 13 built-in themes (light and dark palettes), TTS, and document handling controls
|
|
||||||
|
|
||||||
## 🧭 Key Docs
|
## 🧭 Key Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,40 +3,30 @@ title: Environment Variables
|
||||||
toc_max_heading_level: 3
|
toc_max_heading_level: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
This is the single reference page for OpenReader environment variables.
|
This page is the source-of-truth reference for OpenReader environment variables.
|
||||||
|
|
||||||
|
:::note Recommended configuration path
|
||||||
|
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
|
## Quick Reference Table
|
||||||
|
|
||||||
| Variable | Area | Default | When to set |
|
| Variable | Area | Default | When to set |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION` | Client feature flags | `true` unless set to `false` | Set `false` to hide DOCX support |
|
| `LOG_FORMAT` | Runtime logging | `pretty` | Set `json` for structured logs |
|
||||||
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
| `LOG_LEVEL` | Runtime logging | `info` | Set app server log level |
|
||||||
| `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB` | Client feature flags | `true` unless set to `false` | Set `false` to hide the TTS Provider settings tab |
|
| `API_BASE` | TTS provider bootstrap seed | unset | Optional first-boot base URL for `default-openai` |
|
||||||
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
| `API_KEY` | TTS provider bootstrap seed | unset | Optional first-boot API key for `default-openai` |
|
||||||
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
| `BASE_URL` | Auth | unset | Required at startup |
|
||||||
| `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS` | Client feature flags | `true` unless set to `false` | Set `false` to restrict DeepInfra models |
|
| `AUTH_SECRET` | Auth | unset | Required at startup |
|
||||||
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
|
||||||
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
|
||||||
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
|
||||||
| `API_KEY` | TTS provider | `none` fallback in TTS route | Set when provider requires auth |
|
|
||||||
| `TTS_CACHE_MAX_SIZE_BYTES` | TTS caching | `268435456` (256 MB) | Tune in-memory TTS cache size |
|
|
||||||
| `TTS_CACHE_TTL_MS` | TTS caching | `1800000` (30 min) | Tune in-memory TTS cache TTL |
|
|
||||||
| `TTS_MAX_RETRIES` | TTS retry | `2` | Tune retry attempts for upstream 429/5xx |
|
|
||||||
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
|
||||||
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
|
||||||
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
|
||||||
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
|
||||||
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
|
||||||
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
|
||||||
| `TTS_IP_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `100000` | Override anonymous IP backstop daily limit |
|
|
||||||
| `TTS_IP_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `1000000` | Override authenticated IP backstop daily limit |
|
|
||||||
| `BASE_URL` | Auth | unset | Required (with `AUTH_SECRET`) to enable auth |
|
|
||||||
| `AUTH_SECRET` | Auth | unset | Required (with `BASE_URL`) to enable auth |
|
|
||||||
| `AUTH_TRUSTED_ORIGINS` | Auth | empty | Add extra allowed origins |
|
| `AUTH_TRUSTED_ORIGINS` | Auth | empty | Add extra allowed origins |
|
||||||
| `USE_ANONYMOUS_AUTH_SESSIONS` | Auth | `false` | Set `true` to enable anonymous auth sessions |
|
| `USE_ANONYMOUS_AUTH_SESSIONS` | Auth | `false` | Set `true` to allow anonymous auth sessions |
|
||||||
| `GITHUB_CLIENT_ID` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_SECRET` to enable GitHub sign-in |
|
| `GITHUB_CLIENT_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 |
|
| `GITHUB_CLIENT_SECRET` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_ID` to enable GitHub sign-in |
|
||||||
| `DISABLE_AUTH_RATE_LIMIT` | Rate limiting | `false` | Set `true` to disable auth-layer rate limiting |
|
| `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 |
|
| `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 |
|
| `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_DIR` | Storage | `docstore/seaweedfs` | Override embedded SeaweedFS data directory |
|
||||||
|
|
@ -48,158 +38,121 @@ This is the single reference page for OpenReader environment variables.
|
||||||
| `S3_ENDPOINT` | Storage | derived in embedded mode | Set for S3-compatible providers (MinIO/SeaweedFS/R2/etc.) |
|
| `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_FORCE_PATH_STYLE` | Storage | `true` in embedded mode | Set per provider requirement |
|
||||||
| `S3_PREFIX` | Storage | `openreader` | Customize object key prefix |
|
| `S3_PREFIX` | Storage | `openreader` | Customize object key prefix |
|
||||||
| `RUN_DRIZZLE_MIGRATIONS` | Database migrations | `true` | Set `false` to skip startup Drizzle schema migrations |
|
|
||||||
| `RUN_FS_MIGRATIONS` | Storage migrations | `true` | Set `false` to skip startup filesystem -> S3/DB migration pass |
|
|
||||||
| `IMPORT_LIBRARY_DIR` | Library import | `docstore/library` fallback | Set a single server library root |
|
| `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) |
|
| `IMPORT_LIBRARY_DIRS` | Library import | unset | Set multiple roots (comma/colon/semicolon separated) |
|
||||||
| `WHISPER_CPP_BIN` | Word timing | unset | Set to enable `whisper.cpp` timestamps |
|
| `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 |
|
| `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
|
## TTS Provider and Request Behavior
|
||||||
|
|
||||||
### API_BASE
|
### API_BASE
|
||||||
|
|
||||||
Server-level default base URL for OpenAI-compatible TTS API requests.
|
Optional first-boot bootstrap base URL for the auto-created `default-openai` shared provider.
|
||||||
|
|
||||||
- Example: `http://host.docker.internal:8880/v1`
|
- Example: `http://host.docker.internal:8880/v1`
|
||||||
- Used when no `API_BASE` is set in the user's Settings modal
|
- Read only for provider bootstrap when shared providers are empty. Setting `API_BASE` is sufficient; `API_KEY` may be blank.
|
||||||
- If the user sets `API_BASE` in **Settings → TTS Provider**, that value takes precedence
|
- After bootstrap, provider configuration is DB-backed and managed in **Settings → Admin → Shared providers**.
|
||||||
- Related docs: [TTS Providers](../configure/tts-providers)
|
|
||||||
|
|
||||||
### API_KEY
|
### API_KEY
|
||||||
|
|
||||||
Server-level default API key for TTS provider requests.
|
Optional first-boot bootstrap API key for the auto-created `default-openai` shared provider.
|
||||||
|
|
||||||
- Example: your provider token, or omit if the provider doesn't require auth
|
- Read only for provider bootstrap when shared providers are empty.
|
||||||
- Used when no `API_KEY` is set in the user's Settings modal
|
- Stored encrypted at rest after bootstrap.
|
||||||
- If the user sets `API_KEY` in **Settings → TTS Provider**, that value takes precedence
|
- After bootstrap, provider configuration is DB-backed and managed in **Settings → Admin → Shared providers**.
|
||||||
- Related docs: [TTS Providers](../configure/tts-providers)
|
|
||||||
|
|
||||||
### TTS_CACHE_MAX_SIZE_BYTES
|
|
||||||
|
|
||||||
Maximum in-memory TTS audio cache size in bytes.
|
|
||||||
|
|
||||||
- Default: `268435456` (256 MB)
|
|
||||||
|
|
||||||
### TTS_CACHE_TTL_MS
|
|
||||||
|
|
||||||
In-memory TTS audio cache TTL in milliseconds.
|
|
||||||
|
|
||||||
- Default: `1800000` (30 minutes)
|
|
||||||
|
|
||||||
### TTS_MAX_RETRIES
|
|
||||||
|
|
||||||
Maximum retries for upstream TTS failures (429/5xx).
|
|
||||||
|
|
||||||
- Default: `2`
|
|
||||||
|
|
||||||
### TTS_RETRY_INITIAL_MS
|
|
||||||
|
|
||||||
Initial retry delay in milliseconds for TTS upstream requests.
|
|
||||||
|
|
||||||
- Default: `250`
|
|
||||||
|
|
||||||
### TTS_RETRY_MAX_MS
|
|
||||||
|
|
||||||
Maximum retry delay in milliseconds.
|
|
||||||
|
|
||||||
- Default: `2000`
|
|
||||||
|
|
||||||
### TTS_RETRY_BACKOFF
|
|
||||||
|
|
||||||
Exponential backoff multiplier between retries.
|
|
||||||
|
|
||||||
- Default: `2`
|
|
||||||
|
|
||||||
### TTS_ENABLE_RATE_LIMIT
|
|
||||||
|
|
||||||
Controls TTS character rate limiting in the TTS API.
|
|
||||||
|
|
||||||
- Default: `false` (TTS char limits disabled)
|
|
||||||
- Set to `true` to enforce `TTS_DAILY_LIMIT_*` and `TTS_IP_DAILY_LIMIT_*`
|
|
||||||
- For behavior details and examples, see [TTS Rate Limiting](../configure/tts-rate-limiting)
|
|
||||||
|
|
||||||
### TTS_DAILY_LIMIT_ANONYMOUS
|
|
||||||
|
|
||||||
Anonymous per-user daily character limit.
|
|
||||||
|
|
||||||
- Default: `50000`
|
|
||||||
|
|
||||||
### TTS_DAILY_LIMIT_AUTHENTICATED
|
|
||||||
|
|
||||||
Authenticated per-user daily character limit.
|
|
||||||
|
|
||||||
- Default: `500000`
|
|
||||||
|
|
||||||
### TTS_IP_DAILY_LIMIT_ANONYMOUS
|
|
||||||
|
|
||||||
Anonymous IP backstop daily character limit.
|
|
||||||
|
|
||||||
- Default: `100000`
|
|
||||||
|
|
||||||
### TTS_IP_DAILY_LIMIT_AUTHENTICATED
|
|
||||||
|
|
||||||
Authenticated IP backstop daily character limit.
|
|
||||||
|
|
||||||
- Default: `1000000`
|
|
||||||
|
|
||||||
## Auth and Identity
|
## Auth and Identity
|
||||||
|
|
||||||
### BASE_URL
|
### BASE_URL
|
||||||
|
|
||||||
External base URL for this OpenReader instance.
|
Required external base URL for this OpenReader instance.
|
||||||
|
|
||||||
- Required with `AUTH_SECRET` to enable auth
|
- Required at startup
|
||||||
- Example: `http://localhost:3003` or `https://reader.example.com`
|
- Example: `http://localhost:3003` or `https://reader.example.com`
|
||||||
- Related docs: [Auth](../configure/auth)
|
|
||||||
|
|
||||||
### AUTH_SECRET
|
### AUTH_SECRET
|
||||||
|
|
||||||
Secret key used by auth/session handling.
|
Required secret key used by auth/session handling.
|
||||||
|
|
||||||
- Required with `BASE_URL` to enable auth
|
- Required at startup
|
||||||
- Generate with `openssl rand -hex 32`
|
- Generate with `openssl rand -base64 32`
|
||||||
- Related docs: [Auth](../configure/auth)
|
|
||||||
|
|
||||||
### AUTH_TRUSTED_ORIGINS
|
### AUTH_TRUSTED_ORIGINS
|
||||||
|
|
||||||
Additional allowed origins for auth requests.
|
Additional allowed origins for auth requests.
|
||||||
|
|
||||||
- Comma-separated list
|
- Comma-separated list
|
||||||
- `BASE_URL` origin is always trusted automatically
|
- `BASE_URL` origin is trusted automatically
|
||||||
- Related docs: [Auth](../configure/auth)
|
|
||||||
|
|
||||||
### USE_ANONYMOUS_AUTH_SESSIONS
|
### USE_ANONYMOUS_AUTH_SESSIONS
|
||||||
|
|
||||||
Controls whether auth-enabled deployments can create/use anonymous sessions.
|
Controls whether auth-enabled deployments can create/use anonymous sessions.
|
||||||
|
|
||||||
- Default: `false` (anonymous sessions disabled)
|
- Default: `false`
|
||||||
- Set `true` to allow anonymous sessions and guest-style flows
|
|
||||||
- When `false`, users must sign in or sign up with an account
|
|
||||||
- Related docs: [Auth](../configure/auth)
|
|
||||||
|
|
||||||
### GITHUB_CLIENT_ID
|
### GITHUB_CLIENT_ID
|
||||||
|
|
||||||
GitHub OAuth client ID.
|
GitHub OAuth client ID.
|
||||||
|
|
||||||
- Enable only with `GITHUB_CLIENT_SECRET`
|
- Set with `GITHUB_CLIENT_SECRET`
|
||||||
|
|
||||||
### GITHUB_CLIENT_SECRET
|
### GITHUB_CLIENT_SECRET
|
||||||
|
|
||||||
GitHub OAuth client secret.
|
GitHub OAuth client secret.
|
||||||
|
|
||||||
- Enable only with `GITHUB_CLIENT_ID`
|
- Set with `GITHUB_CLIENT_ID`
|
||||||
|
|
||||||
### DISABLE_AUTH_RATE_LIMIT
|
### ADMIN_EMAILS
|
||||||
|
|
||||||
Controls Better Auth rate limiting.
|
Comma-separated list of email addresses auto-promoted to admin.
|
||||||
|
|
||||||
- Default behavior: auth-layer rate limiting enabled
|
- Requires auth to be enabled
|
||||||
- Set to `true` to disable auth-layer rate limiting
|
- Admins can manage shared providers and runtime site features in-app
|
||||||
- This does not affect TTS character rate limiting
|
|
||||||
- Related docs: [Auth](../configure/auth)
|
### 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 -base64 32`.
|
||||||
|
- Self-hosted Node.js deployments run the scheduler in-process and do not require this variable.
|
||||||
|
|
||||||
## Database and Object Blob Storage
|
## Database and Object Blob Storage
|
||||||
|
|
||||||
|
|
@ -209,7 +162,6 @@ Switches metadata/auth storage from SQLite to Postgres.
|
||||||
|
|
||||||
- Unset: SQLite at `docstore/sqlite3.db`
|
- Unset: SQLite at `docstore/sqlite3.db`
|
||||||
- Set: Postgres mode
|
- Set: Postgres mode
|
||||||
- Related docs: [Database](../configure/database)
|
|
||||||
|
|
||||||
### USE_EMBEDDED_WEED_MINI
|
### USE_EMBEDDED_WEED_MINI
|
||||||
|
|
||||||
|
|
@ -217,190 +169,280 @@ Controls embedded SeaweedFS startup.
|
||||||
|
|
||||||
- Default behavior: treated as enabled when unset
|
- Default behavior: treated as enabled when unset
|
||||||
- Set `false` to rely on external S3-compatible storage
|
- Set `false` to rely on external S3-compatible storage
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### WEED_MINI_DIR
|
### WEED_MINI_DIR
|
||||||
|
|
||||||
Data directory for embedded SeaweedFS (`weed mini`).
|
Data directory for embedded SeaweedFS (`weed mini`).
|
||||||
|
|
||||||
- Default: `docstore/seaweedfs`
|
- Default: `docstore/seaweedfs`
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### WEED_MINI_WAIT_SEC
|
### WEED_MINI_WAIT_SEC
|
||||||
|
|
||||||
Maximum seconds to wait for embedded SeaweedFS startup.
|
Max wait time for embedded SeaweedFS startup.
|
||||||
|
|
||||||
- Default: `20`
|
- Default: `20`
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### S3_ACCESS_KEY_ID
|
### S3_ACCESS_KEY_ID
|
||||||
|
|
||||||
Access key for S3-compatible storage.
|
S3 access key.
|
||||||
|
|
||||||
- Auto-generated in embedded mode if unset
|
- Optional in embedded mode (auto-generated when unset)
|
||||||
- Set explicitly for stable credentials or external providers
|
- Required for external S3 mode
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### S3_SECRET_ACCESS_KEY
|
### S3_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
Secret key for S3-compatible storage.
|
S3 secret key.
|
||||||
|
|
||||||
- Auto-generated in embedded mode if unset
|
- Optional in embedded mode (auto-generated when unset)
|
||||||
- Set explicitly for stable credentials or external providers
|
- Required for external S3 mode
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### S3_BUCKET
|
### S3_BUCKET
|
||||||
|
|
||||||
Bucket name used for document blobs.
|
S3 bucket name.
|
||||||
|
|
||||||
- Default in embedded mode: `openreader-documents`
|
- Embedded default: `openreader-documents`
|
||||||
- Required for external S3-compatible storage
|
- Required for external S3 mode
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### S3_REGION
|
### S3_REGION
|
||||||
|
|
||||||
Region used by the S3 client.
|
S3 region.
|
||||||
|
|
||||||
- Default in embedded mode: `us-east-1`
|
- Embedded default: `us-east-1`
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
- Required for external S3 mode
|
||||||
|
|
||||||
### S3_ENDPOINT
|
### S3_ENDPOINT
|
||||||
|
|
||||||
Endpoint URL for S3-compatible storage.
|
Custom endpoint for S3-compatible providers.
|
||||||
|
|
||||||
- In embedded mode, defaults to `http://<BASE_URL host>:8333` (or detected host)
|
- Optional for AWS
|
||||||
- For AWS S3, usually leave unset
|
- Typical for MinIO/SeaweedFS/R2
|
||||||
- For MinIO/SeaweedFS/R2/B2-style APIs, typically set explicitly
|
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### S3_FORCE_PATH_STYLE
|
### S3_FORCE_PATH_STYLE
|
||||||
|
|
||||||
Path-style S3 addressing toggle.
|
Force path-style S3 URLs.
|
||||||
|
|
||||||
- Default in embedded mode: `true`
|
- Embedded default: `true`
|
||||||
- Set according to provider requirements
|
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
### S3_PREFIX
|
### S3_PREFIX
|
||||||
|
|
||||||
Prefix prepended to stored object keys.
|
Object key prefix.
|
||||||
|
|
||||||
- Default: `openreader`
|
- Default: `openreader`
|
||||||
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
## Migration Controls
|
|
||||||
|
|
||||||
### RUN_DRIZZLE_MIGRATIONS
|
|
||||||
|
|
||||||
Controls startup migration execution in shared entrypoint.
|
|
||||||
|
|
||||||
- Default: `true`
|
|
||||||
- Set `false` to skip automatic startup Drizzle schema migrations
|
|
||||||
- Related docs: [Migrations](../configure/migrations), [Database](../configure/database)
|
|
||||||
|
|
||||||
### RUN_FS_MIGRATIONS
|
|
||||||
|
|
||||||
Controls startup filesystem-to-object-store migration execution in shared entrypoint.
|
|
||||||
|
|
||||||
- Default: `true`
|
|
||||||
- Runs `scripts/migrate-fs-v2.mjs` at startup after DB migrations
|
|
||||||
- Set `false` to skip automatic storage migration pass
|
|
||||||
- Related docs: [Migrations](../configure/migrations), [Database](../configure/database), [Object / Blob Storage](../configure/object-blob-storage)
|
|
||||||
|
|
||||||
## Library Import
|
## Library Import
|
||||||
|
|
||||||
### IMPORT_LIBRARY_DIR
|
### IMPORT_LIBRARY_DIR
|
||||||
|
|
||||||
Single directory root for server library import.
|
Single library source directory.
|
||||||
|
|
||||||
- Used when `IMPORT_LIBRARY_DIRS` is unset
|
|
||||||
- Default fallback root: `docstore/library`
|
|
||||||
- Related docs: [Server Library Import](../configure/server-library-import)
|
|
||||||
|
|
||||||
### IMPORT_LIBRARY_DIRS
|
### IMPORT_LIBRARY_DIRS
|
||||||
|
|
||||||
Multiple library roots for server library import.
|
Multiple library roots.
|
||||||
|
|
||||||
- Separator: comma, colon, or semicolon
|
- Supports comma, colon, or semicolon-separated values
|
||||||
- Takes precedence over `IMPORT_LIBRARY_DIR`
|
|
||||||
- Related docs: [Server Library Import](../configure/server-library-import)
|
|
||||||
|
|
||||||
## Audio Tooling and Alignment
|
## Compute Worker and Model Configuration
|
||||||
|
|
||||||
### WHISPER_CPP_BIN
|
### EMBEDDED_COMPUTE_WORKER_PORT
|
||||||
|
|
||||||
Absolute path to compiled `whisper.cpp` binary for word-level timestamps.
|
Embedded compute worker port.
|
||||||
|
|
||||||
- Example: `/whisper.cpp/build/bin/whisper-cli`
|
- Default: `8081`
|
||||||
- Required only for optional word-by-word highlighting
|
|
||||||
|
### 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
|
### FFMPEG_BIN
|
||||||
|
|
||||||
Absolute path or executable name for the ffmpeg binary used by audiobook/processing routes.
|
Override ffmpeg binary path used for audio processing.
|
||||||
|
|
||||||
- Resolution order: `FFMPEG_BIN` -> `ffmpeg-static`
|
- Used by audiobook processing routes and compute worker Whisper audio decode.
|
||||||
- Example: `/var/task/node_modules/ffmpeg-static/ffmpeg`
|
|
||||||
|
|
||||||
## Client Runtime and Feature Flags
|
## Testing and CI
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_DOCX_CONVERSION
|
### DISABLE_AUTH_RATE_LIMIT
|
||||||
|
|
||||||
Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
|
|
||||||
|
|
||||||
- Default: `true` (enabled)
|
|
||||||
- Set `false` to hide DOCX support in the upload UI
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS
|
|
||||||
|
|
||||||
Controls whether the "Delete all user docs" and other bulk-delete buttons are shown in Settings.
|
|
||||||
|
|
||||||
- Default: `true` (enabled)
|
|
||||||
- Set `false` to hide destructive actions (recommended for production)
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB
|
Disables Better Auth request rate limiting.
|
||||||
|
|
||||||
Controls whether the **TTS Provider** section appears in the Settings modal.
|
- Default: `false`
|
||||||
|
|
||||||
- Default: `true` (enabled)
|
### ENABLE_TEST_NAMESPACE
|
||||||
- Set `false` to hide provider/model/API controls in Settings
|
|
||||||
- Useful when you want provider config locked to environment defaults
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_DEFAULT_TTS_PROVIDER
|
|
||||||
|
|
||||||
Sets the default TTS provider for new users.
|
|
||||||
|
|
||||||
- Default: `custom-openai`
|
|
||||||
- Example values: `deepinfra`, `openai`, `custom-openai`
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_DEFAULT_TTS_MODEL
|
|
||||||
|
|
||||||
Sets the default TTS model for new users.
|
|
||||||
|
|
||||||
- Default: `kokoro`
|
|
||||||
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS
|
|
||||||
|
|
||||||
Controls whether the DeepInfra model list shows all models or just the free tier when no API key is set.
|
|
||||||
|
|
||||||
- Default: `true` (show all)
|
|
||||||
- Set `false` to restrict to free tier models when no API key is provided
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
Enables the `x-openreader-test-namespace` header path in production builds.
|
||||||
|
|
||||||
Controls whether audiobook export UI/actions are shown in the client.
|
## Migration Controls
|
||||||
|
|
||||||
- Default behavior: enabled unless explicitly set to `false`
|
### RUN_DRIZZLE_MIGRATIONS
|
||||||
- Applies in both development and production
|
|
||||||
- Affects export entry points in PDF/EPUB pages and document settings UI
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT
|
Controls startup Drizzle schema migrations.
|
||||||
|
|
||||||
Controls word-by-word highlighting UI and timestamp-alignment behavior.
|
- Default: `true`
|
||||||
|
- Set `false` to skip startup migration run
|
||||||
|
|
||||||
- Default behavior: enabled unless explicitly set to `false`
|
### RUN_FS_MIGRATIONS
|
||||||
- Applies in both development and production
|
|
||||||
- Requires working timestamp generation (for example `WHISPER_CPP_BIN`)
|
Controls startup filesystem-to-S3/DB migration pass.
|
||||||
- Affects:
|
|
||||||
- Word-highlight toggles in document settings
|
- Default: `true`
|
||||||
- Alignment requests during TTS playback
|
- 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)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ title: Stack
|
||||||
|
|
||||||
## Framework
|
## Framework
|
||||||
|
|
||||||
- [Next.js](https://nextjs.org/) 15 (App Router)
|
- [Next.js](https://nextjs.org/) 15 (App Router, Turbopack in dev)
|
||||||
- [React](https://react.dev/) 19
|
- [React](https://react.dev/) 19
|
||||||
- [TypeScript](https://www.typescriptlang.org/)
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
- [pnpm](https://pnpm.io/) workspaces monorepo
|
||||||
|
|
||||||
## Containerization and runtime
|
## Containerization and runtime
|
||||||
|
|
||||||
|
|
@ -17,24 +18,47 @@ title: Stack
|
||||||
|
|
||||||
- UI: [Tailwind CSS](https://tailwindcss.com), [Headless UI](https://headlessui.com), [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin)
|
- UI: [Tailwind CSS](https://tailwindcss.com), [Headless UI](https://headlessui.com), [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin)
|
||||||
- Interactions: `react-dnd`, `react-dropzone`
|
- 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
|
- Authentication: [Better Auth](https://www.better-auth.com/) client SDK
|
||||||
- Local storage/cache: [Dexie.js](https://dexie.org/) (IndexedDB)
|
- Local storage/cache: [Dexie.js](https://dexie.org/) (IndexedDB)
|
||||||
|
- Audio playback: [Howler.js](https://howlerjs.com/)
|
||||||
|
- Notifications: `react-hot-toast`
|
||||||
- Document rendering:
|
- Document rendering:
|
||||||
- PDF: [react-pdf](https://github.com/wojtekmaj/react-pdf), [pdf.js](https://mozilla.github.io/pdf.js/)
|
- 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/)
|
- 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)
|
- 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)
|
- 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
|
## Next.js server
|
||||||
|
|
||||||
- APIs: Route Handlers for sync, blob/content access, migrations, audiobook export, TTS/Whisper proxying
|
- APIs: Route Handlers for sync, blob/content access, migrations, audiobook export, TTS/Whisper proxying
|
||||||
- State sync: request-based today (not realtime push updates)
|
- State sync: request-based today (not realtime push updates)
|
||||||
- Authentication: [Better Auth](https://www.better-auth.com/) server handlers/adapters
|
- 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`)
|
- 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
|
- 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
|
- 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
|
- Blob storage: embedded [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (`weed mini`) by default, or external S3-compatible storage via AWS SDK v3
|
||||||
- Audio/processing pipeline: OpenAI-compatible TTS providers, [ffmpeg](https://ffmpeg.org/) for audiobook assembly, optional [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for word timestamps
|
- 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)
|
||||||
|
|
||||||
|
Standalone worker package:
|
||||||
|
|
||||||
|
- **`@openreader/compute-worker`** — standalone Node.js compute service containing its private inference and queue runtime
|
||||||
|
- 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`
|
||||||
|
- HTTP server: [Fastify](https://fastify.dev/) v5 with a versioned OpenAPI contract
|
||||||
|
- 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/)
|
||||||
|
- The Next.js app communicates with the worker only through the versioned HTTP API generated from OpenAPI.
|
||||||
|
|
||||||
## Tooling and testing
|
## Tooling and testing
|
||||||
|
|
||||||
|
|
@ -42,3 +66,4 @@ title: Stack
|
||||||
- TypeScript
|
- TypeScript
|
||||||
- [Playwright](https://playwright.dev/) end-to-end tests
|
- [Playwright](https://playwright.dev/) end-to-end tests
|
||||||
- Drizzle migration/generation scripts
|
- Drizzle migration/generation scripts
|
||||||
|
- [Docusaurus](https://docusaurus.io/) documentation site (`docs-site/`)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"deploy": "docusaurus deploy"
|
"deploy": "docusaurus deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mdx-js/react": "^3.1.1",
|
||||||
"@docusaurus/core": "^3.9.2",
|
"@docusaurus/core": "^3.9.2",
|
||||||
"@docusaurus/preset-classic": "^3.9.2",
|
"@docusaurus/preset-classic": "^3.9.2",
|
||||||
"@easyops-cn/docusaurus-search-local": "^0.54.1",
|
"@easyops-cn/docusaurus-search-local": "^0.54.1",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ importers:
|
||||||
'@easyops-cn/docusaurus-search-local':
|
'@easyops-cn/docusaurus-search-local':
|
||||||
specifier: ^0.54.1
|
specifier: ^0.54.1
|
||||||
version: 0.54.1(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.13)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.1(@types/react@19.2.13)(react@18.3.1))(@types/react@19.2.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)
|
version: 0.54.1(@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@19.2.13)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mdx-js/react@3.1.1(@types/react@19.2.13)(react@18.3.1))(@types/react@19.2.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)
|
||||||
|
'@mdx-js/react':
|
||||||
|
specifier: ^3.1.1
|
||||||
|
version: 3.1.1(@types/react@19.2.13)(react@18.3.1)
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
|
@ -1433,24 +1436,28 @@ packages:
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@node-rs/jieba-linux-arm64-musl@1.10.4':
|
'@node-rs/jieba-linux-arm64-musl@1.10.4':
|
||||||
resolution: {integrity: sha512-Y/tiJ1+HeS5nnmLbZOE+66LbsPOHZ/PUckAYVeLlQfpygLEpLYdlh0aPpS5uiaWMjAXYZYdFkpZHhxDmSLpwpw==}
|
resolution: {integrity: sha512-Y/tiJ1+HeS5nnmLbZOE+66LbsPOHZ/PUckAYVeLlQfpygLEpLYdlh0aPpS5uiaWMjAXYZYdFkpZHhxDmSLpwpw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@node-rs/jieba-linux-x64-gnu@1.10.4':
|
'@node-rs/jieba-linux-x64-gnu@1.10.4':
|
||||||
resolution: {integrity: sha512-WZO8ykRJpWGE9MHuZpy1lu3nJluPoeB+fIJJn5CWZ9YTVhNDWoCF4i/7nxz1ntulINYGQ8VVuCU9LD86Mek97g==}
|
resolution: {integrity: sha512-WZO8ykRJpWGE9MHuZpy1lu3nJluPoeB+fIJJn5CWZ9YTVhNDWoCF4i/7nxz1ntulINYGQ8VVuCU9LD86Mek97g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@node-rs/jieba-linux-x64-musl@1.10.4':
|
'@node-rs/jieba-linux-x64-musl@1.10.4':
|
||||||
resolution: {integrity: sha512-uBBD4S1rGKcgCyAk6VCKatEVQb6EDD5I40v/DxODi5CuZVCANi9m5oee/MQbAoaX7RydA2f0OSCE9/tcwXEwUg==}
|
resolution: {integrity: sha512-uBBD4S1rGKcgCyAk6VCKatEVQb6EDD5I40v/DxODi5CuZVCANi9m5oee/MQbAoaX7RydA2f0OSCE9/tcwXEwUg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@node-rs/jieba-wasm32-wasi@1.10.4':
|
'@node-rs/jieba-wasm32-wasi@1.10.4':
|
||||||
resolution: {integrity: sha512-Y2umiKHjuIJy0uulNDz9SDYHdfq5Hmy7jY5nORO99B4pySKkcrMjpeVrmWXJLIsEKLJwcCXHxz8tjwU5/uhz0A==}
|
resolution: {integrity: sha512-Y2umiKHjuIJy0uulNDz9SDYHdfq5Hmy7jY5nORO99B4pySKkcrMjpeVrmWXJLIsEKLJwcCXHxz8tjwU5/uhz0A==}
|
||||||
|
|
@ -2006,6 +2013,7 @@ packages:
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
|
||||||
|
|
||||||
'@webassemblyjs/ast@1.14.1':
|
'@webassemblyjs/ast@1.14.1':
|
||||||
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
||||||
|
|
@ -5356,6 +5364,7 @@ packages:
|
||||||
|
|
||||||
uuid@8.3.2:
|
uuid@8.3.2:
|
||||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||||
|
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
value-equal@1.0.1:
|
value-equal@1.0.1:
|
||||||
|
|
|
||||||
6
docs-site/pnpm-workspace.yaml
Normal file
6
docs-site/pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
packages:
|
||||||
|
- .
|
||||||
|
|
||||||
|
allowBuilds:
|
||||||
|
core-js: true
|
||||||
|
core-js-pure: true
|
||||||
|
|
@ -23,8 +23,11 @@ const sidebars: SidebarsConfig = {
|
||||||
'configure/tts-provider-guides/kokoro-fastapi',
|
'configure/tts-provider-guides/kokoro-fastapi',
|
||||||
'configure/tts-provider-guides/kitten-tts-fastapi',
|
'configure/tts-provider-guides/kitten-tts-fastapi',
|
||||||
'configure/tts-provider-guides/orpheus-fastapi',
|
'configure/tts-provider-guides/orpheus-fastapi',
|
||||||
|
'configure/tts-provider-guides/supertonic',
|
||||||
|
'configure/tts-provider-guides/replicate',
|
||||||
'configure/tts-provider-guides/deepinfra',
|
'configure/tts-provider-guides/deepinfra',
|
||||||
'configure/tts-provider-guides/openai',
|
'configure/tts-provider-guides/openai',
|
||||||
|
'configure/tts-provider-guides/speech-sdk',
|
||||||
'configure/tts-provider-guides/other',
|
'configure/tts-provider-guides/other',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -33,6 +36,11 @@ const sidebars: SidebarsConfig = {
|
||||||
id: 'configure/auth',
|
id: 'configure/auth',
|
||||||
label: '🔐 Auth',
|
label: '🔐 Auth',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'configure/admin-panel',
|
||||||
|
label: '🛡️ Admin Panel',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
id: 'configure/server-library-import',
|
id: 'configure/server-library-import',
|
||||||
|
|
@ -47,7 +55,12 @@ const sidebars: SidebarsConfig = {
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
label: '🚀 Deploy',
|
label: '🚀 Deploy',
|
||||||
items: ['deploy/local-development', 'deploy/vercel-deployment'],
|
items: [
|
||||||
|
'deploy/local-development',
|
||||||
|
'deploy/docker-compose',
|
||||||
|
'deploy/compute-worker',
|
||||||
|
'deploy/vercel-deployment',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
|
||||||
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
||||||
NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false
|
|
||||||
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
||||||
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
||||||
# Optional (non-AWS S3-compatible providers):
|
# Optional (non-AWS S3-compatible providers):
|
||||||
|
|
@ -49,7 +48,6 @@ We recommend setting these defaults for a production-like environment:
|
||||||
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
||||||
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
||||||
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
||||||
- `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false`: Restricts usage to free models if no key is provided
|
|
||||||
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
||||||
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
||||||
:::
|
:::
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ This is the single reference page for OpenReader environment variables.
|
||||||
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
||||||
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
||||||
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
||||||
| `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS` | Client feature flags | `true` unless set to `false` | Set `false` to restrict DeepInfra models |
|
|
||||||
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
||||||
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
||||||
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
||||||
|
|
@ -24,6 +23,7 @@ This is the single reference page for OpenReader environment variables.
|
||||||
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
||||||
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
||||||
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
||||||
|
| `TTS_UPSTREAM_TIMEOUT_MS` | TTS request timeout | `285000` | Set max upstream TTS request duration before fail-fast |
|
||||||
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
||||||
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
||||||
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
||||||
|
|
@ -110,6 +110,14 @@ Exponential backoff multiplier between retries.
|
||||||
|
|
||||||
- Default: `2`
|
- Default: `2`
|
||||||
|
|
||||||
|
### TTS_UPSTREAM_TIMEOUT_MS
|
||||||
|
|
||||||
|
Maximum upstream TTS request timeout in milliseconds.
|
||||||
|
|
||||||
|
- Default: `285000` (285 seconds)
|
||||||
|
- Applies to outbound provider calls from server routes using shared TTS generation
|
||||||
|
- Increase for slower providers/models; decrease to fail fast and surface retryable errors sooner
|
||||||
|
|
||||||
### TTS_ENABLE_RATE_LIMIT
|
### TTS_ENABLE_RATE_LIMIT
|
||||||
|
|
||||||
Controls TTS character rate limiting in the TTS API.
|
Controls TTS character rate limiting in the TTS API.
|
||||||
|
|
@ -367,13 +375,6 @@ Absolute path or executable name for the ffmpeg binary used by audiobook/process
|
||||||
|
|
||||||
- Default: `kokoro`
|
- Default: `kokoro`
|
||||||
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
||||||
|
|
||||||
### NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS
|
|
||||||
|
|
||||||
Controls whether the DeepInfra model list shows all models or just the free tier when no API key is set.
|
|
||||||
|
|
||||||
- Default: `true` (show all)
|
|
||||||
- Set `false` to restrict to free tier models when no API key is provided
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
|
||||||
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
||||||
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
||||||
NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false
|
|
||||||
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
||||||
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
||||||
# Optional (non-AWS S3-compatible providers):
|
# Optional (non-AWS S3-compatible providers):
|
||||||
|
|
@ -49,7 +48,6 @@ We recommend setting these defaults for a production-like environment:
|
||||||
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
||||||
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
||||||
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
||||||
- `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false`: Restricts usage to free models if no key is provided
|
|
||||||
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
||||||
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
||||||
:::
|
:::
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ This is the single reference page for OpenReader environment variables.
|
||||||
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
||||||
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
||||||
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
||||||
| `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS` | Client feature flags | `true` unless set to `false` | Set `false` to restrict DeepInfra models |
|
|
||||||
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
||||||
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
||||||
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
||||||
|
|
@ -24,6 +23,7 @@ This is the single reference page for OpenReader environment variables.
|
||||||
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
||||||
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
||||||
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
||||||
|
| `TTS_UPSTREAM_TIMEOUT_MS` | TTS request timeout | `285000` | Set max upstream TTS request duration before fail-fast |
|
||||||
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
||||||
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
||||||
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
||||||
|
|
@ -110,6 +110,14 @@ Exponential backoff multiplier between retries.
|
||||||
|
|
||||||
- Default: `2`
|
- Default: `2`
|
||||||
|
|
||||||
|
### TTS_UPSTREAM_TIMEOUT_MS
|
||||||
|
|
||||||
|
Maximum upstream TTS request timeout in milliseconds.
|
||||||
|
|
||||||
|
- Default: `285000` (285 seconds)
|
||||||
|
- Applies to outbound provider calls from server routes using shared TTS generation
|
||||||
|
- Increase for slower providers/models; decrease to fail fast and surface retryable errors sooner
|
||||||
|
|
||||||
### TTS_ENABLE_RATE_LIMIT
|
### TTS_ENABLE_RATE_LIMIT
|
||||||
|
|
||||||
Controls TTS character rate limiting in the TTS API.
|
Controls TTS character rate limiting in the TTS API.
|
||||||
|
|
@ -367,13 +375,6 @@ Absolute path or executable name for the ffmpeg binary used by audiobook/process
|
||||||
|
|
||||||
- Default: `kokoro`
|
- Default: `kokoro`
|
||||||
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
||||||
|
|
||||||
### NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS
|
|
||||||
|
|
||||||
Controls whether the DeepInfra model list shows all models or just the free tier when no API key is set.
|
|
||||||
|
|
||||||
- Default: `true` (show all)
|
|
||||||
- Set `false` to restrict to free tier models when no API key is provided
|
|
||||||
|
|
||||||
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
- [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
|
||||||
|
- [ffmpeg](https://ffmpeg.org)
|
||||||
|
- [react-pdf](https://github.com/wojtekmaj/react-pdf)
|
||||||
|
- [react-reader](https://github.com/happyr/react-reader)
|
||||||
7
docs-site/versioned_docs/version-v2.1.0/about/license.md
Normal file
7
docs-site/versioned_docs/version-v2.1.0/about/license.md
Normal file
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
46
docs-site/versioned_docs/version-v2.1.0/configure/auth.md
Normal file
46
docs-site/versioned_docs/version-v2.1.0/configure/auth.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
title: Auth
|
||||||
|
---
|
||||||
|
|
||||||
|
This page covers application-level configuration for provider access and authentication.
|
||||||
|
|
||||||
|
## Auth behavior
|
||||||
|
|
||||||
|
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
||||||
|
- Remove either value to disable auth.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Route behavior
|
||||||
|
|
||||||
|
- `/` is a public landing/onboarding page and remains indexable.
|
||||||
|
- `/app` is the protected app home (document list and uploader UI).
|
||||||
|
- If auth is enabled and a valid session exists (including anonymous), visiting `/` redirects to `/app`.
|
||||||
|
- 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 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.
|
||||||
|
|
||||||
|
### Auth disabled
|
||||||
|
|
||||||
|
- Settings and reading progress stay local in the browser (Dexie/IndexedDB).
|
||||||
|
- This avoids no-auth cross-browser conflicts, but there is no cross-device sync.
|
||||||
|
|
||||||
|
## Claim modal note
|
||||||
|
|
||||||
|
- You may still see old anonymous settings/progress available to claim from older deployments.
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
title: Database
|
||||||
|
---
|
||||||
|
|
||||||
|
This page covers database mode selection for OpenReader.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
|
||||||
|
- With auth disabled, settings and reading progress remain local in the browser.
|
||||||
|
- Sync is currently request-based (not realtime push invalidation).
|
||||||
132
docs-site/versioned_docs/version-v2.1.0/configure/migrations.md
Normal file
132
docs-site/versioned_docs/version-v2.1.0/configure/migrations.md
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<Tabs groupId="apply-migration-commands">
|
||||||
|
<TabItem value="project-scripts" label="Project Scripts" default>
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<Tabs groupId="generate-migration-commands">
|
||||||
|
<TabItem value="project-script" label="Project Script" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full pipeline: Better Auth CLI + Drizzle generate (both dialects)
|
||||||
|
pnpm generate
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||||
|
|
||||||
|
```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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- [Database](./database)
|
||||||
|
- [Object / Blob Storage](./object-blob-storage)
|
||||||
|
- [Migration Environment Variables](../reference/environment-variables#migration-controls)
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Storage backends
|
||||||
|
|
||||||
|
- Embedded (default): SQLite metadata + embedded SeaweedFS (`weed mini`) blobs.
|
||||||
|
- External: Postgres + external S3-compatible object storage.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<Tabs groupId="audiobook-storage-access-cli">
|
||||||
|
<TabItem value="aws-s3" label="AWS S3" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all audiobook objects
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive
|
||||||
|
|
||||||
|
# Filter to one book id (replace <book-id>)
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive | grep "<book-id>-audiobook/"
|
||||||
|
|
||||||
|
# Download one object by full key
|
||||||
|
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/<path>/<file>.m4b" "./audiobook.m4b"
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="s3-compatible" label="Embedded / MinIO / R2 / etc">
|
||||||
|
|
||||||
|
```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 <book-id>)
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive --endpoint-url "$S3_ENDPOINT" | grep "<book-id>-audiobook/"
|
||||||
|
|
||||||
|
# Download one object by full key
|
||||||
|
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/<path>/<file>.m4b" "./audiobook.m4b" --endpoint-url "$S3_ENDPOINT"
|
||||||
|
```
|
||||||
|
|
||||||
|
Embedded default example: `S3_ENDPOINT=http://127.0.0.1:8333` (or your mapped host/port).
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
title: DeepInfra
|
||||||
|
---
|
||||||
|
|
||||||
|
Use DeepInfra's hosted TTS models as your provider.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=https://api.deepinfra.com/v1/openai
|
||||||
|
API_KEY=your-deepinfra-key
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or in-app via Settings → TTS Provider:**
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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).
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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`.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
title: OpenAI
|
||||||
|
---
|
||||||
|
|
||||||
|
Use the OpenAI TTS API as your provider.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=https://api.openai.com/v1
|
||||||
|
API_KEY=sk-...
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=openai
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or in-app via Settings → TTS Provider:**
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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).
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
:::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)
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
title: TTS Providers
|
||||||
|
---
|
||||||
|
|
||||||
|
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. You choose your provider and credentials in one of two places:
|
||||||
|
|
||||||
|
**Environment variables**: set in your `.env` or `docker-compose.yml` as server-level defaults. Applied when the user has no saved Settings.
|
||||||
|
|
||||||
|
**Settings modal** (Settings > TTS Provider): stored in the browser and sent with every TTS request. **Overrides env vars.**
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Set env vars as deployment-level defaults. Users (or you, in a single-user setup) can then change the provider, base URL, and API key from the Settings modal without redeploying. Clearing the Settings fields falls back to the env var defaults.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
- **OpenAI**: Cloud. Base URL pre-filled (`https://api.openai.com/v1`). 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` and `Deepinfra` you only need to supply an API key. For `Custom OpenAI-Like` you must also set `API_BASE`.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
- [DeepInfra](./tts-provider-guides/deepinfra)
|
||||||
|
- [OpenAI](./tts-provider-guides/openai)
|
||||||
|
- [Other](./tts-provider-guides/other)
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [TTS Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||||
|
- [TTS Rate Limiting](./tts-rate-limiting)
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
title: TTS Rate Limiting
|
||||||
|
---
|
||||||
|
|
||||||
|
This page explains OpenReader's TTS character rate limiting controls.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- TTS rate limiting is disabled by default.
|
||||||
|
- To enable it, set `TTS_ENABLE_RATE_LIMIT=true`.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Enable/disable:
|
||||||
|
|
||||||
|
- `TTS_ENABLE_RATE_LIMIT` (default: `false`)
|
||||||
|
|
||||||
|
Per-user daily limits:
|
||||||
|
|
||||||
|
- `TTS_DAILY_LIMIT_ANONYMOUS` (default: `50000`)
|
||||||
|
- `TTS_DAILY_LIMIT_AUTHENTICATED` (default: `500000`)
|
||||||
|
|
||||||
|
IP backstop daily limits:
|
||||||
|
|
||||||
|
- `TTS_IP_DAILY_LIMIT_ANONYMOUS` (default: `100000`)
|
||||||
|
- `TTS_IP_DAILY_LIMIT_AUTHENTICATED` (default: `1000000`)
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- TTS/rate-limit environment variables: [Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||||
|
- Auth configuration: [Auth](./auth)
|
||||||
|
- Provider setup: [TTS Providers](./tts-providers)
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
---
|
||||||
|
title: Local Development
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js (recommended with [nvm](https://github.com/nvm-sh/nvm))
|
||||||
|
- `pnpm` (recommended) or `npm`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
- A reachable TTS API server
|
||||||
|
- [SeaweedFS](https://github.com/seaweedfs/seaweedfs) `weed` binary (required unless using external S3 storage)
|
||||||
|
|
||||||
|
<Tabs groupId="seaweedfs-install">
|
||||||
|
<TabItem value="macos" label="macOS" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install seaweedfs
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="linux" label="Linux">
|
||||||
|
|
||||||
|
Install the `weed` binary from the [SeaweedFS releases](https://github.com/seaweedfs/seaweedfs/releases) and ensure it is available on `PATH`.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Optional, depending on features:
|
||||||
|
|
||||||
|
- [libreoffice](https://www.libreoffice.org) (required for DOCX conversion)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install libreoffice
|
||||||
|
```
|
||||||
|
|
||||||
|
- [whisper.cpp](https://github.com/ggml-org/whisper.cpp) (optional, for word-by-word highlighting)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clone and build whisper.cpp (no model download needed – OpenReader handles that)
|
||||||
|
git clone https://github.com/ggml-org/whisper.cpp.git
|
||||||
|
cd whisper.cpp
|
||||||
|
cmake -B build
|
||||||
|
cmake --build build -j --config Release
|
||||||
|
|
||||||
|
# point OpenReader to the compiled whisper-cli binary
|
||||||
|
echo WHISPER_CPP_BIN="$(pwd)/build/bin/whisper-cli"
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Set `WHISPER_CPP_BIN` in your `.env` to enable word-by-word highlighting.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
- No auth mode: leave `BASE_URL` or `AUTH_SECRET` unset.
|
||||||
|
- Auth enabled mode: set both `BASE_URL` (typically `http://localhost:3003`) and `AUTH_SECRET` (generate with `openssl rand -hex 32`).
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
- `AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://192.168.0.116:3003`
|
||||||
|
- Stable S3 credentials via `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`
|
||||||
|
- External S3 storage by setting `USE_EMBEDDED_WEED_MINI=false` and related S3 vars
|
||||||
|
|
||||||
|
:::info
|
||||||
|
For all environment variables, see [Environment Variables](../reference/environment-variables).
|
||||||
|
:::
|
||||||
|
|
||||||
|
See [Auth](../configure/auth) for app/auth behavior.
|
||||||
|
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).
|
||||||
|
|
||||||
|
4. Run DB migrations.
|
||||||
|
|
||||||
|
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
|
||||||
|
- You only need manual migration commands for one-off troubleshooting or explicit migration workflows:
|
||||||
|
|
||||||
|
```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`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
5. Start the app.
|
||||||
|
|
||||||
|
<Tabs groupId="local-run-mode">
|
||||||
|
<TabItem value="dev" label="Dev" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="prod" label="Build + Start">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
:::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).
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
---
|
||||||
|
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`.
|
||||||
|
|
||||||
|
:::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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
API_BASE=https://api.deepinfra.com/v1/openai
|
||||||
|
API_KEY=your_deepinfra_key
|
||||||
|
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
|
||||||
|
BASE_URL=https://your-app.vercel.app
|
||||||
|
AUTH_SECRET=...
|
||||||
|
# Optional client/runtime feature defaults:
|
||||||
|
NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
|
||||||
|
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
||||||
|
NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
||||||
|
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
||||||
|
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
||||||
|
# Optional (non-AWS S3-compatible providers):
|
||||||
|
# S3_ENDPOINT=https://...
|
||||||
|
# S3_FORCE_PATH_STYLE=true
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info Production Configuration & Feature Flags
|
||||||
|
We recommend setting these defaults for a production-like environment:
|
||||||
|
|
||||||
|
- `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false`: Disables DOCX upload (requires external tools anyway)
|
||||||
|
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
||||||
|
- `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false`: Hides the Settings -> TTS Provider section
|
||||||
|
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
||||||
|
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
||||||
|
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
||||||
|
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning Auth recommendation
|
||||||
|
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 2. 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/audiobook/status`
|
||||||
|
- `/api/whisper`
|
||||||
|
|
||||||
|
:::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.
|
||||||
|
|
||||||
|
## 3. 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/whisper/route.ts": { "memory": 3009 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Adjust memory per route if your files are larger or your plan differs.
|
||||||
|
|
||||||
|
## 4. Runtime expectations and caveats
|
||||||
|
|
||||||
|
- Audiobook APIs require S3 configuration; otherwise they return `503`.
|
||||||
|
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
|
||||||
|
- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
|
||||||
|
- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
|
||||||
|
|
||||||
|
## 5. 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. If using word highlighting, verify timestamps are produced and rendered.
|
||||||
121
docs-site/versioned_docs/version-v2.1.0/docker-quick-start.md
Normal file
121
docs-site/versioned_docs/version-v2.1.0/docker-quick-start.md
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
---
|
||||||
|
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, KittenTTS-FastAPI, Orpheus-FastAPI, DeepInfra, OpenAI, or equivalent)
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If you have suitable hardware, you can run Kokoro locally with Docker. See [Kokoro-FastAPI](./configure/tts-provider-guides/kokoro-fastapi).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 1. Start the Docker container
|
||||||
|
|
||||||
|
<Tabs groupId="docker-start-mode">
|
||||||
|
<TabItem value="minimal" label="Minimal" default>
|
||||||
|
|
||||||
|
Auth disabled, embedded storage ephemeral, no library import:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name openreader \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 3003:3003 \
|
||||||
|
-p 8333:8333 \
|
||||||
|
ghcr.io/richardr1126/openreader:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="localhost" label="Localhost">
|
||||||
|
|
||||||
|
Persistent storage, embedded SeaweedFS `weed mini`, optional auth, optional library mount:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name openreader \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 3003:3003 \
|
||||||
|
-p 8333:8333 \
|
||||||
|
-v openreader_docstore:/app/docstore \
|
||||||
|
-v /path/to/your/library:/app/docstore/library:ro \
|
||||||
|
-e API_BASE=http://host.docker.internal:8880/v1 \
|
||||||
|
-e API_KEY=none \
|
||||||
|
-e BASE_URL=http://localhost:3003 \
|
||||||
|
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||||
|
ghcr.io/richardr1126/openreader:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-network" label="LAN Host">
|
||||||
|
|
||||||
|
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://<YOUR_LAN_IP>:3003 \
|
||||||
|
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||||
|
-e AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 \
|
||||||
|
-e USE_ANONYMOUS_AUTH_SESSIONS=true \
|
||||||
|
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.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
:::tip Quick Tips
|
||||||
|
- Remove `/app/docstore/library` if you do not need server library import.
|
||||||
|
- Remove either `BASE_URL` or `AUTH_SECRET` to keep auth disabled.
|
||||||
|
- Set `API_BASE` to your reachable TTS server base URL.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::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`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info Auth and Migrations
|
||||||
|
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
||||||
|
- DB/storage migrations run automatically at container startup via the shared entrypoint.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info Related Docs
|
||||||
|
- [Environment Variables](./reference/environment-variables)
|
||||||
|
- [Auth](./configure/auth)
|
||||||
|
- [Database](./configure/database)
|
||||||
|
- [Object / Blob Storage](./configure/object-blob-storage)
|
||||||
|
- [Migrations](./configure/migrations)
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 2. Configure settings in the app UI
|
||||||
|
|
||||||
|
- Set TTS provider and model in Settings
|
||||||
|
- Set TTS API base URL and API key if needed
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
```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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Visit [http://localhost:3003](http://localhost:3003) after startup.
|
||||||
52
docs-site/versioned_docs/version-v2.1.0/introduction.md
Normal file
52
docs-site/versioned_docs/version-v2.1.0/introduction.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
id: intro
|
||||||
|
title: Introduction
|
||||||
|
slug: /
|
||||||
|
---
|
||||||
|
|
||||||
|
OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
|
||||||
|
|
||||||
|
> Previously named **OpenReader-WebUI**.
|
||||||
|
|
||||||
|
It supports multiple TTS providers including OpenAI, 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
|
||||||
|
|
||||||
|
- 🎯 **Multi-Provider TTS Support**
|
||||||
|
- [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI): supports multi-voice combinations (for example `af_heart+af_bella`)
|
||||||
|
- [**KittenTTS-FastAPI**](https://github.com/richardr1126/KittenTTS-FastAPI): lightweight, CPU-friendly self-hosted TTS
|
||||||
|
- [**Orpheus-FastAPI**](https://github.com/Lex-au/Orpheus-FastAPI)
|
||||||
|
- **Custom OpenAI-compatible**: any TTS API with `/v1/audio/voices` and `/v1/audio/speech` endpoints
|
||||||
|
- **Cloud TTS providers**:
|
||||||
|
- [**DeepInfra**](https://deepinfra.com/models/text-to-speech): Kokoro-82M and other hosted models
|
||||||
|
- [**OpenAI API**](https://platform.openai.com/docs/pricing#transcription-and-speech): `tts-1`, `tts-1-hd`, and `gpt-4o-mini-tts`
|
||||||
|
- 🛜 **Server-side Document Storage**
|
||||||
|
- Documents are persisted in server blob/object storage for consistent access
|
||||||
|
- 📚 **External Library Import**
|
||||||
|
- Import documents from server-mounted folders
|
||||||
|
- 🎧 **Server-side Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
|
||||||
|
- 📖 **Read Along Experience**
|
||||||
|
- Real-time highlighting for PDF/EPUB, with optional word-level [whisper.cpp](https://github.com/ggml-org/whisper.cpp) timestamps
|
||||||
|
- 🔐 **Auth Optional by Design**
|
||||||
|
- Run no-auth for local use, or enable auth with user isolation and claim flow
|
||||||
|
- 🗂️ **Flexible Storage and Database Modes** with embedded defaults or external S3/Postgres
|
||||||
|
- 🚀 **Production-ready Server Behavior** with TTS caching/retries/rate limits and startup migrations
|
||||||
|
- 🎨 **Customizable Experience**
|
||||||
|
- 13 built-in themes (light and dark palettes), TTS, 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)
|
||||||
|
|
@ -0,0 +1,407 @@
|
||||||
|
---
|
||||||
|
title: Environment Variables
|
||||||
|
toc_max_heading_level: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the single reference page for OpenReader environment variables.
|
||||||
|
|
||||||
|
## Quick Reference Table
|
||||||
|
|
||||||
|
| Variable | Area | Default | When to set |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION` | Client feature flags | `true` unless set to `false` | Set `false` to hide DOCX support |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB` | Client feature flags | `true` unless set to `false` | Set `false` to hide the TTS Provider settings tab |
|
||||||
|
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
||||||
|
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
||||||
|
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
||||||
|
| `API_KEY` | TTS provider | `none` fallback in TTS route | Set when provider requires auth |
|
||||||
|
| `TTS_CACHE_MAX_SIZE_BYTES` | TTS caching | `268435456` (256 MB) | Tune in-memory TTS cache size |
|
||||||
|
| `TTS_CACHE_TTL_MS` | TTS caching | `1800000` (30 min) | Tune in-memory TTS cache TTL |
|
||||||
|
| `TTS_MAX_RETRIES` | TTS retry | `2` | Tune retry attempts for upstream 429/5xx |
|
||||||
|
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
||||||
|
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
||||||
|
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
||||||
|
| `TTS_UPSTREAM_TIMEOUT_MS` | TTS request timeout | `285000` | Set max upstream TTS request duration before fail-fast |
|
||||||
|
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
||||||
|
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
||||||
|
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
||||||
|
| `TTS_IP_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `100000` | Override anonymous IP backstop daily limit |
|
||||||
|
| `TTS_IP_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `1000000` | Override authenticated IP backstop daily limit |
|
||||||
|
| `BASE_URL` | Auth | unset | Required (with `AUTH_SECRET`) to enable auth |
|
||||||
|
| `AUTH_SECRET` | Auth | unset | Required (with `BASE_URL`) to enable auth |
|
||||||
|
| `AUTH_TRUSTED_ORIGINS` | Auth | empty | Add extra allowed origins |
|
||||||
|
| `USE_ANONYMOUS_AUTH_SESSIONS` | Auth | `false` | Set `true` to enable anonymous auth sessions |
|
||||||
|
| `GITHUB_CLIENT_ID` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_SECRET` to enable GitHub sign-in |
|
||||||
|
| `GITHUB_CLIENT_SECRET` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_ID` to enable GitHub sign-in |
|
||||||
|
| `DISABLE_AUTH_RATE_LIMIT` | Rate limiting | `false` | Set `true` to disable auth-layer rate limiting |
|
||||||
|
| `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 |
|
||||||
|
| `RUN_DRIZZLE_MIGRATIONS` | Database migrations | `true` | Set `false` to skip startup Drizzle schema migrations |
|
||||||
|
| `RUN_FS_MIGRATIONS` | Storage migrations | `true` | Set `false` to skip startup filesystem -> S3/DB migration pass |
|
||||||
|
| `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) |
|
||||||
|
| `WHISPER_CPP_BIN` | Word timing | unset | Set to enable `whisper.cpp` timestamps |
|
||||||
|
| `FFMPEG_BIN` | Audio runtime | auto-detected (`ffmpeg-static`) | Override ffmpeg binary path |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## TTS Provider and Request Behavior
|
||||||
|
|
||||||
|
### API_BASE
|
||||||
|
|
||||||
|
Server-level default base URL for OpenAI-compatible TTS API requests.
|
||||||
|
|
||||||
|
- Example: `http://host.docker.internal:8880/v1`
|
||||||
|
- Used when no `API_BASE` is set in the user's Settings modal
|
||||||
|
- If the user sets `API_BASE` in **Settings → TTS Provider**, that value takes precedence
|
||||||
|
- Related docs: [TTS Providers](../configure/tts-providers)
|
||||||
|
|
||||||
|
### API_KEY
|
||||||
|
|
||||||
|
Server-level default API key for TTS provider requests.
|
||||||
|
|
||||||
|
- Example: your provider token, or omit if the provider doesn't require auth
|
||||||
|
- Used when no `API_KEY` is set in the user's Settings modal
|
||||||
|
- If the user sets `API_KEY` in **Settings → TTS Provider**, that value takes precedence
|
||||||
|
- Related docs: [TTS Providers](../configure/tts-providers)
|
||||||
|
|
||||||
|
### TTS_CACHE_MAX_SIZE_BYTES
|
||||||
|
|
||||||
|
Maximum in-memory TTS audio cache size in bytes.
|
||||||
|
|
||||||
|
- Default: `268435456` (256 MB)
|
||||||
|
|
||||||
|
### TTS_CACHE_TTL_MS
|
||||||
|
|
||||||
|
In-memory TTS audio cache TTL in milliseconds.
|
||||||
|
|
||||||
|
- Default: `1800000` (30 minutes)
|
||||||
|
|
||||||
|
### TTS_MAX_RETRIES
|
||||||
|
|
||||||
|
Maximum retries for upstream TTS failures (429/5xx).
|
||||||
|
|
||||||
|
- Default: `2`
|
||||||
|
|
||||||
|
### TTS_RETRY_INITIAL_MS
|
||||||
|
|
||||||
|
Initial retry delay in milliseconds for TTS upstream requests.
|
||||||
|
|
||||||
|
- Default: `250`
|
||||||
|
|
||||||
|
### TTS_RETRY_MAX_MS
|
||||||
|
|
||||||
|
Maximum retry delay in milliseconds.
|
||||||
|
|
||||||
|
- Default: `2000`
|
||||||
|
|
||||||
|
### TTS_RETRY_BACKOFF
|
||||||
|
|
||||||
|
Exponential backoff multiplier between retries.
|
||||||
|
|
||||||
|
- Default: `2`
|
||||||
|
|
||||||
|
### TTS_UPSTREAM_TIMEOUT_MS
|
||||||
|
|
||||||
|
Maximum upstream TTS request timeout in milliseconds.
|
||||||
|
|
||||||
|
- Default: `285000` (285 seconds)
|
||||||
|
- Applies to outbound provider calls from server routes using shared TTS generation
|
||||||
|
- Increase for slower providers/models; decrease to fail fast and surface retryable errors sooner
|
||||||
|
|
||||||
|
### TTS_ENABLE_RATE_LIMIT
|
||||||
|
|
||||||
|
Controls TTS character rate limiting in the TTS API.
|
||||||
|
|
||||||
|
- Default: `false` (TTS char limits disabled)
|
||||||
|
- Set to `true` to enforce `TTS_DAILY_LIMIT_*` and `TTS_IP_DAILY_LIMIT_*`
|
||||||
|
- For behavior details and examples, see [TTS Rate Limiting](../configure/tts-rate-limiting)
|
||||||
|
|
||||||
|
### TTS_DAILY_LIMIT_ANONYMOUS
|
||||||
|
|
||||||
|
Anonymous per-user daily character limit.
|
||||||
|
|
||||||
|
- Default: `50000`
|
||||||
|
|
||||||
|
### TTS_DAILY_LIMIT_AUTHENTICATED
|
||||||
|
|
||||||
|
Authenticated per-user daily character limit.
|
||||||
|
|
||||||
|
- Default: `500000`
|
||||||
|
|
||||||
|
### TTS_IP_DAILY_LIMIT_ANONYMOUS
|
||||||
|
|
||||||
|
Anonymous IP backstop daily character limit.
|
||||||
|
|
||||||
|
- Default: `100000`
|
||||||
|
|
||||||
|
### TTS_IP_DAILY_LIMIT_AUTHENTICATED
|
||||||
|
|
||||||
|
Authenticated IP backstop daily character limit.
|
||||||
|
|
||||||
|
- Default: `1000000`
|
||||||
|
|
||||||
|
## Auth and Identity
|
||||||
|
|
||||||
|
### BASE_URL
|
||||||
|
|
||||||
|
External base URL for this OpenReader instance.
|
||||||
|
|
||||||
|
- Required with `AUTH_SECRET` to enable auth
|
||||||
|
- Example: `http://localhost:3003` or `https://reader.example.com`
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### AUTH_SECRET
|
||||||
|
|
||||||
|
Secret key used by auth/session handling.
|
||||||
|
|
||||||
|
- Required with `BASE_URL` to enable auth
|
||||||
|
- Generate with `openssl rand -hex 32`
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### AUTH_TRUSTED_ORIGINS
|
||||||
|
|
||||||
|
Additional allowed origins for auth requests.
|
||||||
|
|
||||||
|
- Comma-separated list
|
||||||
|
- `BASE_URL` origin is always trusted automatically
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### USE_ANONYMOUS_AUTH_SESSIONS
|
||||||
|
|
||||||
|
Controls whether auth-enabled deployments can create/use anonymous sessions.
|
||||||
|
|
||||||
|
- Default: `false` (anonymous sessions disabled)
|
||||||
|
- Set `true` to allow anonymous sessions and guest-style flows
|
||||||
|
- When `false`, users must sign in or sign up with an account
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### GITHUB_CLIENT_ID
|
||||||
|
|
||||||
|
GitHub OAuth client ID.
|
||||||
|
|
||||||
|
- Enable only with `GITHUB_CLIENT_SECRET`
|
||||||
|
|
||||||
|
### GITHUB_CLIENT_SECRET
|
||||||
|
|
||||||
|
GitHub OAuth client secret.
|
||||||
|
|
||||||
|
- Enable only with `GITHUB_CLIENT_ID`
|
||||||
|
|
||||||
|
### DISABLE_AUTH_RATE_LIMIT
|
||||||
|
|
||||||
|
Controls Better Auth rate limiting.
|
||||||
|
|
||||||
|
- Default behavior: auth-layer rate limiting enabled
|
||||||
|
- Set to `true` to disable auth-layer rate limiting
|
||||||
|
- This does not affect TTS character rate limiting
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
## Database and Object Blob Storage
|
||||||
|
|
||||||
|
### POSTGRES_URL
|
||||||
|
|
||||||
|
Switches metadata/auth storage from SQLite to Postgres.
|
||||||
|
|
||||||
|
- Unset: SQLite at `docstore/sqlite3.db`
|
||||||
|
- Set: Postgres mode
|
||||||
|
- Related docs: [Database](../configure/database)
|
||||||
|
|
||||||
|
### USE_EMBEDDED_WEED_MINI
|
||||||
|
|
||||||
|
Controls embedded SeaweedFS startup.
|
||||||
|
|
||||||
|
- Default behavior: treated as enabled when unset
|
||||||
|
- Set `false` to rely on external S3-compatible storage
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### WEED_MINI_DIR
|
||||||
|
|
||||||
|
Data directory for embedded SeaweedFS (`weed mini`).
|
||||||
|
|
||||||
|
- Default: `docstore/seaweedfs`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### WEED_MINI_WAIT_SEC
|
||||||
|
|
||||||
|
Maximum seconds to wait for embedded SeaweedFS startup.
|
||||||
|
|
||||||
|
- Default: `20`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_ACCESS_KEY_ID
|
||||||
|
|
||||||
|
Access key for S3-compatible storage.
|
||||||
|
|
||||||
|
- Auto-generated in embedded mode if unset
|
||||||
|
- Set explicitly for stable credentials or external providers
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
|
Secret key for S3-compatible storage.
|
||||||
|
|
||||||
|
- Auto-generated in embedded mode if unset
|
||||||
|
- Set explicitly for stable credentials or external providers
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_BUCKET
|
||||||
|
|
||||||
|
Bucket name used for document blobs.
|
||||||
|
|
||||||
|
- Default in embedded mode: `openreader-documents`
|
||||||
|
- Required for external S3-compatible storage
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_REGION
|
||||||
|
|
||||||
|
Region used by the S3 client.
|
||||||
|
|
||||||
|
- Default in embedded mode: `us-east-1`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_ENDPOINT
|
||||||
|
|
||||||
|
Endpoint URL for S3-compatible storage.
|
||||||
|
|
||||||
|
- In embedded mode, defaults to `http://<BASE_URL host>:8333` (or detected host)
|
||||||
|
- For AWS S3, usually leave unset
|
||||||
|
- For MinIO/SeaweedFS/R2/B2-style APIs, typically set explicitly
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_FORCE_PATH_STYLE
|
||||||
|
|
||||||
|
Path-style S3 addressing toggle.
|
||||||
|
|
||||||
|
- Default in embedded mode: `true`
|
||||||
|
- Set according to provider requirements
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_PREFIX
|
||||||
|
|
||||||
|
Prefix prepended to stored object keys.
|
||||||
|
|
||||||
|
- Default: `openreader`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
## Migration Controls
|
||||||
|
|
||||||
|
### RUN_DRIZZLE_MIGRATIONS
|
||||||
|
|
||||||
|
Controls startup migration execution in shared entrypoint.
|
||||||
|
|
||||||
|
- Default: `true`
|
||||||
|
- Set `false` to skip automatic startup Drizzle schema migrations
|
||||||
|
- Related docs: [Migrations](../configure/migrations), [Database](../configure/database)
|
||||||
|
|
||||||
|
### RUN_FS_MIGRATIONS
|
||||||
|
|
||||||
|
Controls startup filesystem-to-object-store migration execution in shared entrypoint.
|
||||||
|
|
||||||
|
- Default: `true`
|
||||||
|
- Runs `scripts/migrate-fs-v2.mjs` at startup after DB migrations
|
||||||
|
- Set `false` to skip automatic storage migration pass
|
||||||
|
- Related docs: [Migrations](../configure/migrations), [Database](../configure/database), [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
## Library Import
|
||||||
|
|
||||||
|
### IMPORT_LIBRARY_DIR
|
||||||
|
|
||||||
|
Single directory root for server library import.
|
||||||
|
|
||||||
|
- Used when `IMPORT_LIBRARY_DIRS` is unset
|
||||||
|
- Default fallback root: `docstore/library`
|
||||||
|
- Related docs: [Server Library Import](../configure/server-library-import)
|
||||||
|
|
||||||
|
### IMPORT_LIBRARY_DIRS
|
||||||
|
|
||||||
|
Multiple library roots for server library import.
|
||||||
|
|
||||||
|
- Separator: comma, colon, or semicolon
|
||||||
|
- Takes precedence over `IMPORT_LIBRARY_DIR`
|
||||||
|
- Related docs: [Server Library Import](../configure/server-library-import)
|
||||||
|
|
||||||
|
## Audio Tooling and Alignment
|
||||||
|
|
||||||
|
### WHISPER_CPP_BIN
|
||||||
|
|
||||||
|
Absolute path to compiled `whisper.cpp` binary for word-level timestamps.
|
||||||
|
|
||||||
|
- Example: `/whisper.cpp/build/bin/whisper-cli`
|
||||||
|
- Required only for optional word-by-word highlighting
|
||||||
|
|
||||||
|
### FFMPEG_BIN
|
||||||
|
|
||||||
|
Absolute path or executable name for the ffmpeg binary used by audiobook/processing routes.
|
||||||
|
|
||||||
|
- Resolution order: `FFMPEG_BIN` -> `ffmpeg-static`
|
||||||
|
- Example: `/var/task/node_modules/ffmpeg-static/ffmpeg`
|
||||||
|
|
||||||
|
## Client Runtime and Feature Flags
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_DOCX_CONVERSION
|
||||||
|
|
||||||
|
Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
|
||||||
|
|
||||||
|
- Default: `true` (enabled)
|
||||||
|
- Set `false` to hide DOCX support in the upload UI
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS
|
||||||
|
|
||||||
|
Controls whether the "Delete all user docs" and other bulk-delete buttons are shown in Settings.
|
||||||
|
|
||||||
|
- Default: `true` (enabled)
|
||||||
|
- Set `false` to hide destructive actions (recommended for production)
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB
|
||||||
|
|
||||||
|
Controls whether the **TTS Provider** section appears in the Settings modal.
|
||||||
|
|
||||||
|
- Default: `true` (enabled)
|
||||||
|
- Set `false` to hide provider/model/API controls in Settings
|
||||||
|
- Useful when you want provider config locked to environment defaults
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_DEFAULT_TTS_PROVIDER
|
||||||
|
|
||||||
|
Sets the default TTS provider for new users.
|
||||||
|
|
||||||
|
- Default: `custom-openai`
|
||||||
|
- Example values: `deepinfra`, `openai`, `custom-openai`
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_DEFAULT_TTS_MODEL
|
||||||
|
|
||||||
|
Sets the default TTS model for new users.
|
||||||
|
|
||||||
|
- Default: `kokoro`
|
||||||
|
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
||||||
|
|
||||||
|
Controls whether audiobook export UI/actions are shown in the client.
|
||||||
|
|
||||||
|
- Default behavior: enabled unless explicitly set to `false`
|
||||||
|
- Applies in both development and production
|
||||||
|
- Affects export entry points in PDF/EPUB pages and document settings UI
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT
|
||||||
|
|
||||||
|
Controls word-by-word highlighting UI and timestamp-alignment behavior.
|
||||||
|
|
||||||
|
- Default behavior: enabled unless explicitly set to `false`
|
||||||
|
- Applies in both development and production
|
||||||
|
- Requires working timestamp generation (for example `WHISPER_CPP_BIN`)
|
||||||
|
- Affects:
|
||||||
|
- Word-highlight toggles in document settings
|
||||||
|
- Alignment requests during TTS playback
|
||||||
44
docs-site/versioned_docs/version-v2.1.0/reference/stack.md
Normal file
44
docs-site/versioned_docs/version-v2.1.0/reference/stack.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
title: Stack
|
||||||
|
---
|
||||||
|
|
||||||
|
## Framework
|
||||||
|
|
||||||
|
- [Next.js](https://nextjs.org/) 15 (App Router)
|
||||||
|
- [React](https://react.dev/) 19
|
||||||
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
|
||||||
|
## 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`
|
||||||
|
- Authentication: [Better Auth](https://www.better-auth.com/) client SDK
|
||||||
|
- Local storage/cache: [Dexie.js](https://dexie.org/) (IndexedDB)
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- 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
|
||||||
|
- Audio/processing pipeline: OpenAI-compatible TTS providers, [ffmpeg](https://ffmpeg.org/) for audiobook assembly, optional [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for word timestamps
|
||||||
|
|
||||||
|
## Tooling and testing
|
||||||
|
|
||||||
|
- ESLint
|
||||||
|
- TypeScript
|
||||||
|
- [Playwright](https://playwright.dev/) end-to-end tests
|
||||||
|
- Drizzle migration/generation scripts
|
||||||
|
|
@ -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)
|
||||||
|
- [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
|
||||||
|
- [ffmpeg](https://ffmpeg.org)
|
||||||
|
- [react-pdf](https://github.com/wojtekmaj/react-pdf)
|
||||||
|
- [react-reader](https://github.com/happyr/react-reader)
|
||||||
7
docs-site/versioned_docs/version-v2.1.1/about/license.md
Normal file
7
docs-site/versioned_docs/version-v2.1.1/about/license.md
Normal file
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
46
docs-site/versioned_docs/version-v2.1.1/configure/auth.md
Normal file
46
docs-site/versioned_docs/version-v2.1.1/configure/auth.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
title: Auth
|
||||||
|
---
|
||||||
|
|
||||||
|
This page covers application-level configuration for provider access and authentication.
|
||||||
|
|
||||||
|
## Auth behavior
|
||||||
|
|
||||||
|
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
||||||
|
- Remove either value to disable auth.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Route behavior
|
||||||
|
|
||||||
|
- `/` is a public landing/onboarding page and remains indexable.
|
||||||
|
- `/app` is the protected app home (document list and uploader UI).
|
||||||
|
- If auth is enabled and a valid session exists (including anonymous), visiting `/` redirects to `/app`.
|
||||||
|
- 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 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.
|
||||||
|
|
||||||
|
### Auth disabled
|
||||||
|
|
||||||
|
- Settings and reading progress stay local in the browser (Dexie/IndexedDB).
|
||||||
|
- This avoids no-auth cross-browser conflicts, but there is no cross-device sync.
|
||||||
|
|
||||||
|
## Claim modal note
|
||||||
|
|
||||||
|
- You may still see old anonymous settings/progress available to claim from older deployments.
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
title: Database
|
||||||
|
---
|
||||||
|
|
||||||
|
This page covers database mode selection for OpenReader.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
|
||||||
|
- With auth disabled, settings and reading progress remain local in the browser.
|
||||||
|
- Sync is currently request-based (not realtime push invalidation).
|
||||||
132
docs-site/versioned_docs/version-v2.1.1/configure/migrations.md
Normal file
132
docs-site/versioned_docs/version-v2.1.1/configure/migrations.md
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<Tabs groupId="apply-migration-commands">
|
||||||
|
<TabItem value="project-scripts" label="Project Scripts" default>
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<Tabs groupId="generate-migration-commands">
|
||||||
|
<TabItem value="project-script" label="Project Script" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full pipeline: Better Auth CLI + Drizzle generate (both dialects)
|
||||||
|
pnpm generate
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||||
|
|
||||||
|
```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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- [Database](./database)
|
||||||
|
- [Object / Blob Storage](./object-blob-storage)
|
||||||
|
- [Migration Environment Variables](../reference/environment-variables#migration-controls)
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Storage backends
|
||||||
|
|
||||||
|
- Embedded (default): SQLite metadata + embedded SeaweedFS (`weed mini`) blobs.
|
||||||
|
- External: Postgres + external S3-compatible object storage.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<Tabs groupId="audiobook-storage-access-cli">
|
||||||
|
<TabItem value="aws-s3" label="AWS S3" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all audiobook objects
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive
|
||||||
|
|
||||||
|
# Filter to one book id (replace <book-id>)
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive | grep "<book-id>-audiobook/"
|
||||||
|
|
||||||
|
# Download one object by full key
|
||||||
|
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/<path>/<file>.m4b" "./audiobook.m4b"
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="s3-compatible" label="Embedded / MinIO / R2 / etc">
|
||||||
|
|
||||||
|
```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 <book-id>)
|
||||||
|
aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive --endpoint-url "$S3_ENDPOINT" | grep "<book-id>-audiobook/"
|
||||||
|
|
||||||
|
# Download one object by full key
|
||||||
|
aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/<path>/<file>.m4b" "./audiobook.m4b" --endpoint-url "$S3_ENDPOINT"
|
||||||
|
```
|
||||||
|
|
||||||
|
Embedded default example: `S3_ENDPOINT=http://127.0.0.1:8333` (or your mapped host/port).
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
title: DeepInfra
|
||||||
|
---
|
||||||
|
|
||||||
|
Use DeepInfra's hosted TTS models as your provider.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=https://api.deepinfra.com/v1/openai
|
||||||
|
API_KEY=your-deepinfra-key
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or in-app via Settings → TTS Provider:**
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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).
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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`.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
title: OpenAI
|
||||||
|
---
|
||||||
|
|
||||||
|
Use the OpenAI TTS API as your provider.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```env
|
||||||
|
API_BASE=https://api.openai.com/v1
|
||||||
|
API_KEY=sk-...
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=openai
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or in-app via Settings → TTS Provider:**
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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).
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
**Environment variables (recommended for deployment):**
|
||||||
|
|
||||||
|
```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.
|
||||||
|
|
||||||
|
Settings modal values override env vars. See [TTS Providers](../tts-providers) for how the two layers interact.
|
||||||
|
|
||||||
|
:::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)
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
title: TTS Providers
|
||||||
|
---
|
||||||
|
|
||||||
|
OpenReader routes all TTS requests through the Next.js server to an OpenAI-compatible API. You choose your provider and credentials in one of two places:
|
||||||
|
|
||||||
|
**Environment variables**: set in your `.env` or `docker-compose.yml` as server-level defaults. Applied when the user has no saved Settings.
|
||||||
|
|
||||||
|
**Settings modal** (Settings > TTS Provider): stored in the browser and sent with every TTS request. **Overrides env vars.**
|
||||||
|
|
||||||
|
:::note
|
||||||
|
Set env vars as deployment-level defaults. Users (or you, in a single-user setup) can then change the provider, base URL, and API key from the Settings modal without redeploying. Clearing the Settings fields falls back to the env var defaults.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
- **OpenAI**: Cloud. Base URL pre-filled (`https://api.openai.com/v1`). 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` and `Deepinfra` you only need to supply an API key. For `Custom OpenAI-Like` you must also set `API_BASE`.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
- [DeepInfra](./tts-provider-guides/deepinfra)
|
||||||
|
- [OpenAI](./tts-provider-guides/openai)
|
||||||
|
- [Other](./tts-provider-guides/other)
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [TTS Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||||
|
- [TTS Rate Limiting](./tts-rate-limiting)
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
---
|
||||||
|
title: TTS Rate Limiting
|
||||||
|
---
|
||||||
|
|
||||||
|
This page explains OpenReader's TTS character rate limiting controls.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- TTS rate limiting is disabled by default.
|
||||||
|
- To enable it, set `TTS_ENABLE_RATE_LIMIT=true`.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Enable/disable:
|
||||||
|
|
||||||
|
- `TTS_ENABLE_RATE_LIMIT` (default: `false`)
|
||||||
|
|
||||||
|
Per-user daily limits:
|
||||||
|
|
||||||
|
- `TTS_DAILY_LIMIT_ANONYMOUS` (default: `50000`)
|
||||||
|
- `TTS_DAILY_LIMIT_AUTHENTICATED` (default: `500000`)
|
||||||
|
|
||||||
|
IP backstop daily limits:
|
||||||
|
|
||||||
|
- `TTS_IP_DAILY_LIMIT_ANONYMOUS` (default: `100000`)
|
||||||
|
- `TTS_IP_DAILY_LIMIT_AUTHENTICATED` (default: `1000000`)
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- TTS/rate-limit environment variables: [Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
|
||||||
|
- Auth configuration: [Auth](./auth)
|
||||||
|
- Provider setup: [TTS Providers](./tts-providers)
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
---
|
||||||
|
title: Local Development
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Node.js (recommended with [nvm](https://github.com/nvm-sh/nvm))
|
||||||
|
- `pnpm` (recommended) or `npm`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
- A reachable TTS API server
|
||||||
|
- [SeaweedFS](https://github.com/seaweedfs/seaweedfs) `weed` binary (required unless using external S3 storage)
|
||||||
|
|
||||||
|
<Tabs groupId="seaweedfs-install">
|
||||||
|
<TabItem value="macos" label="macOS" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install seaweedfs
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="linux" label="Linux">
|
||||||
|
|
||||||
|
Install the `weed` binary from the [SeaweedFS releases](https://github.com/seaweedfs/seaweedfs/releases) and ensure it is available on `PATH`.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
Optional, depending on features:
|
||||||
|
|
||||||
|
- [libreoffice](https://www.libreoffice.org) (required for DOCX conversion)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install libreoffice
|
||||||
|
```
|
||||||
|
|
||||||
|
- [whisper.cpp](https://github.com/ggml-org/whisper.cpp) (optional, for word-by-word highlighting)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# clone and build whisper.cpp (no model download needed – OpenReader handles that)
|
||||||
|
git clone https://github.com/ggml-org/whisper.cpp.git
|
||||||
|
cd whisper.cpp
|
||||||
|
cmake -B build
|
||||||
|
cmake --build build -j --config Release
|
||||||
|
|
||||||
|
# point OpenReader to the compiled whisper-cli binary
|
||||||
|
echo WHISPER_CPP_BIN="$(pwd)/build/bin/whisper-cli"
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Set `WHISPER_CPP_BIN` in your `.env` to enable word-by-word highlighting.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
- No auth mode: leave `BASE_URL` or `AUTH_SECRET` unset.
|
||||||
|
- Auth enabled mode: set both `BASE_URL` (typically `http://localhost:3003`) and `AUTH_SECRET` (generate with `openssl rand -hex 32`).
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
- `AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://192.168.0.116:3003`
|
||||||
|
- Stable S3 credentials via `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`
|
||||||
|
- External S3 storage by setting `USE_EMBEDDED_WEED_MINI=false` and related S3 vars
|
||||||
|
|
||||||
|
:::info
|
||||||
|
For all environment variables, see [Environment Variables](../reference/environment-variables).
|
||||||
|
:::
|
||||||
|
|
||||||
|
See [Auth](../configure/auth) for app/auth behavior.
|
||||||
|
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).
|
||||||
|
|
||||||
|
4. Run DB migrations.
|
||||||
|
|
||||||
|
- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
|
||||||
|
- You only need manual migration commands for one-off troubleshooting or explicit migration workflows:
|
||||||
|
|
||||||
|
```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`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
5. Start the app.
|
||||||
|
|
||||||
|
<Tabs groupId="local-run-mode">
|
||||||
|
<TabItem value="dev" label="Dev" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="prod" label="Build + Start">
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
pnpm start
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
:::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).
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
---
|
||||||
|
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`.
|
||||||
|
|
||||||
|
:::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):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
API_BASE=https://api.deepinfra.com/v1/openai
|
||||||
|
API_KEY=your_deepinfra_key
|
||||||
|
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
|
||||||
|
BASE_URL=https://your-app.vercel.app
|
||||||
|
AUTH_SECRET=...
|
||||||
|
# Optional client/runtime feature defaults:
|
||||||
|
NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
|
||||||
|
NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
|
||||||
|
NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
|
||||||
|
NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
|
||||||
|
NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
|
||||||
|
NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
|
||||||
|
# Optional (non-AWS S3-compatible providers):
|
||||||
|
# S3_ENDPOINT=https://...
|
||||||
|
# S3_FORCE_PATH_STYLE=true
|
||||||
|
```
|
||||||
|
|
||||||
|
:::info Production Configuration & Feature Flags
|
||||||
|
We recommend setting these defaults for a production-like environment:
|
||||||
|
|
||||||
|
- `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false`: Disables DOCX upload (requires external tools anyway)
|
||||||
|
- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
|
||||||
|
- `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB=false`: Hides the Settings -> TTS Provider section
|
||||||
|
- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
|
||||||
|
- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
|
||||||
|
- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
|
||||||
|
- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning Auth recommendation
|
||||||
|
For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
For all variables and defaults, see [Environment Variables](../reference/environment-variables).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 2. 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/audiobook/status`
|
||||||
|
- `/api/whisper`
|
||||||
|
|
||||||
|
:::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.
|
||||||
|
|
||||||
|
## 3. 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/whisper/route.ts": { "memory": 3009 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Adjust memory per route if your files are larger or your plan differs.
|
||||||
|
|
||||||
|
## 4. Runtime expectations and caveats
|
||||||
|
|
||||||
|
- Audiobook APIs require S3 configuration; otherwise they return `503`.
|
||||||
|
- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
|
||||||
|
- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
|
||||||
|
- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
|
||||||
|
|
||||||
|
## 5. 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. If using word highlighting, verify timestamps are produced and rendered.
|
||||||
121
docs-site/versioned_docs/version-v2.1.1/docker-quick-start.md
Normal file
121
docs-site/versioned_docs/version-v2.1.1/docker-quick-start.md
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
---
|
||||||
|
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, KittenTTS-FastAPI, Orpheus-FastAPI, DeepInfra, OpenAI, or equivalent)
|
||||||
|
|
||||||
|
:::note
|
||||||
|
If you have suitable hardware, you can run Kokoro locally with Docker. See [Kokoro-FastAPI](./configure/tts-provider-guides/kokoro-fastapi).
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 1. Start the Docker container
|
||||||
|
|
||||||
|
<Tabs groupId="docker-start-mode">
|
||||||
|
<TabItem value="minimal" label="Minimal" default>
|
||||||
|
|
||||||
|
Auth disabled, embedded storage ephemeral, no library import:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name openreader \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 3003:3003 \
|
||||||
|
-p 8333:8333 \
|
||||||
|
ghcr.io/richardr1126/openreader:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="localhost" label="Localhost">
|
||||||
|
|
||||||
|
Persistent storage, embedded SeaweedFS `weed mini`, optional auth, optional library mount:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --name openreader \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-p 3003:3003 \
|
||||||
|
-p 8333:8333 \
|
||||||
|
-v openreader_docstore:/app/docstore \
|
||||||
|
-v /path/to/your/library:/app/docstore/library:ro \
|
||||||
|
-e API_BASE=http://host.docker.internal:8880/v1 \
|
||||||
|
-e API_KEY=none \
|
||||||
|
-e BASE_URL=http://localhost:3003 \
|
||||||
|
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||||
|
ghcr.io/richardr1126/openreader:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="local-network" label="LAN Host">
|
||||||
|
|
||||||
|
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://<YOUR_LAN_IP>:3003 \
|
||||||
|
-e AUTH_SECRET=$(openssl rand -hex 32) \
|
||||||
|
-e AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 \
|
||||||
|
-e USE_ANONYMOUS_AUTH_SESSIONS=true \
|
||||||
|
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.
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
:::tip Quick Tips
|
||||||
|
- Remove `/app/docstore/library` if you do not need server library import.
|
||||||
|
- Remove either `BASE_URL` or `AUTH_SECRET` to keep auth disabled.
|
||||||
|
- Set `API_BASE` to your reachable TTS server base URL.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::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`.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info Auth and Migrations
|
||||||
|
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
||||||
|
- DB/storage migrations run automatically at container startup via the shared entrypoint.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::info Related Docs
|
||||||
|
- [Environment Variables](./reference/environment-variables)
|
||||||
|
- [Auth](./configure/auth)
|
||||||
|
- [Database](./configure/database)
|
||||||
|
- [Object / Blob Storage](./configure/object-blob-storage)
|
||||||
|
- [Migrations](./configure/migrations)
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 2. Configure settings in the app UI
|
||||||
|
|
||||||
|
- Set TTS provider and model in Settings
|
||||||
|
- Set TTS API base URL and API key if needed
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
```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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Visit [http://localhost:3003](http://localhost:3003) after startup.
|
||||||
52
docs-site/versioned_docs/version-v2.1.1/introduction.md
Normal file
52
docs-site/versioned_docs/version-v2.1.1/introduction.md
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
---
|
||||||
|
id: intro
|
||||||
|
title: Introduction
|
||||||
|
slug: /
|
||||||
|
---
|
||||||
|
|
||||||
|
OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
|
||||||
|
|
||||||
|
> Previously named **OpenReader-WebUI**.
|
||||||
|
|
||||||
|
It supports multiple TTS providers including OpenAI, 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
|
||||||
|
|
||||||
|
- 🎯 **Multi-Provider TTS Support**
|
||||||
|
- [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI): supports multi-voice combinations (for example `af_heart+af_bella`)
|
||||||
|
- [**KittenTTS-FastAPI**](https://github.com/richardr1126/KittenTTS-FastAPI): lightweight, CPU-friendly self-hosted TTS
|
||||||
|
- [**Orpheus-FastAPI**](https://github.com/Lex-au/Orpheus-FastAPI)
|
||||||
|
- **Custom OpenAI-compatible**: any TTS API with `/v1/audio/voices` and `/v1/audio/speech` endpoints
|
||||||
|
- **Cloud TTS providers**:
|
||||||
|
- [**DeepInfra**](https://deepinfra.com/models/text-to-speech): Kokoro-82M and other hosted models
|
||||||
|
- [**OpenAI API**](https://platform.openai.com/docs/pricing#transcription-and-speech): `tts-1`, `tts-1-hd`, and `gpt-4o-mini-tts`
|
||||||
|
- 🛜 **Server-side Document Storage**
|
||||||
|
- Documents are persisted in server blob/object storage for consistent access
|
||||||
|
- 📚 **External Library Import**
|
||||||
|
- Import documents from server-mounted folders
|
||||||
|
- 🎧 **Server-side Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
|
||||||
|
- 📖 **Read Along Experience**
|
||||||
|
- Real-time highlighting for PDF/EPUB, with optional word-level [whisper.cpp](https://github.com/ggml-org/whisper.cpp) timestamps
|
||||||
|
- 🔐 **Auth Optional by Design**
|
||||||
|
- Run no-auth for local use, or enable auth with user isolation and claim flow
|
||||||
|
- 🗂️ **Flexible Storage and Database Modes** with embedded defaults or external S3/Postgres
|
||||||
|
- 🚀 **Production-ready Server Behavior** with TTS caching/retries/rate limits and startup migrations
|
||||||
|
- 🎨 **Customizable Experience**
|
||||||
|
- 13 built-in themes (light and dark palettes), TTS, 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)
|
||||||
|
|
@ -0,0 +1,407 @@
|
||||||
|
---
|
||||||
|
title: Environment Variables
|
||||||
|
toc_max_heading_level: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
This is the single reference page for OpenReader environment variables.
|
||||||
|
|
||||||
|
## Quick Reference Table
|
||||||
|
|
||||||
|
| Variable | Area | Default | When to set |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION` | Client feature flags | `true` unless set to `false` | Set `false` to hide DOCX support |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB` | Client feature flags | `true` unless set to `false` | Set `false` to hide the TTS Provider settings tab |
|
||||||
|
| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
|
||||||
|
| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
|
||||||
|
| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
|
||||||
|
| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
|
||||||
|
| `API_KEY` | TTS provider | `none` fallback in TTS route | Set when provider requires auth |
|
||||||
|
| `TTS_CACHE_MAX_SIZE_BYTES` | TTS caching | `268435456` (256 MB) | Tune in-memory TTS cache size |
|
||||||
|
| `TTS_CACHE_TTL_MS` | TTS caching | `1800000` (30 min) | Tune in-memory TTS cache TTL |
|
||||||
|
| `TTS_MAX_RETRIES` | TTS retry | `2` | Tune retry attempts for upstream 429/5xx |
|
||||||
|
| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
|
||||||
|
| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
|
||||||
|
| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
|
||||||
|
| `TTS_UPSTREAM_TIMEOUT_MS` | TTS request timeout | `285000` | Set max upstream TTS request duration before fail-fast |
|
||||||
|
| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
|
||||||
|
| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
|
||||||
|
| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
|
||||||
|
| `TTS_IP_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `100000` | Override anonymous IP backstop daily limit |
|
||||||
|
| `TTS_IP_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `1000000` | Override authenticated IP backstop daily limit |
|
||||||
|
| `BASE_URL` | Auth | unset | Required (with `AUTH_SECRET`) to enable auth |
|
||||||
|
| `AUTH_SECRET` | Auth | unset | Required (with `BASE_URL`) to enable auth |
|
||||||
|
| `AUTH_TRUSTED_ORIGINS` | Auth | empty | Add extra allowed origins |
|
||||||
|
| `USE_ANONYMOUS_AUTH_SESSIONS` | Auth | `false` | Set `true` to enable anonymous auth sessions |
|
||||||
|
| `GITHUB_CLIENT_ID` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_SECRET` to enable GitHub sign-in |
|
||||||
|
| `GITHUB_CLIENT_SECRET` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_ID` to enable GitHub sign-in |
|
||||||
|
| `DISABLE_AUTH_RATE_LIMIT` | Rate limiting | `false` | Set `true` to disable auth-layer rate limiting |
|
||||||
|
| `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 |
|
||||||
|
| `RUN_DRIZZLE_MIGRATIONS` | Database migrations | `true` | Set `false` to skip startup Drizzle schema migrations |
|
||||||
|
| `RUN_FS_MIGRATIONS` | Storage migrations | `true` | Set `false` to skip startup filesystem -> S3/DB migration pass |
|
||||||
|
| `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) |
|
||||||
|
| `WHISPER_CPP_BIN` | Word timing | unset | Set to enable `whisper.cpp` timestamps |
|
||||||
|
| `FFMPEG_BIN` | Audio runtime | auto-detected (`ffmpeg-static`) | Override ffmpeg binary path |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## TTS Provider and Request Behavior
|
||||||
|
|
||||||
|
### API_BASE
|
||||||
|
|
||||||
|
Server-level default base URL for OpenAI-compatible TTS API requests.
|
||||||
|
|
||||||
|
- Example: `http://host.docker.internal:8880/v1`
|
||||||
|
- Used when no `API_BASE` is set in the user's Settings modal
|
||||||
|
- If the user sets `API_BASE` in **Settings → TTS Provider**, that value takes precedence
|
||||||
|
- Related docs: [TTS Providers](../configure/tts-providers)
|
||||||
|
|
||||||
|
### API_KEY
|
||||||
|
|
||||||
|
Server-level default API key for TTS provider requests.
|
||||||
|
|
||||||
|
- Example: your provider token, or omit if the provider doesn't require auth
|
||||||
|
- Used when no `API_KEY` is set in the user's Settings modal
|
||||||
|
- If the user sets `API_KEY` in **Settings → TTS Provider**, that value takes precedence
|
||||||
|
- Related docs: [TTS Providers](../configure/tts-providers)
|
||||||
|
|
||||||
|
### TTS_CACHE_MAX_SIZE_BYTES
|
||||||
|
|
||||||
|
Maximum in-memory TTS audio cache size in bytes.
|
||||||
|
|
||||||
|
- Default: `268435456` (256 MB)
|
||||||
|
|
||||||
|
### TTS_CACHE_TTL_MS
|
||||||
|
|
||||||
|
In-memory TTS audio cache TTL in milliseconds.
|
||||||
|
|
||||||
|
- Default: `1800000` (30 minutes)
|
||||||
|
|
||||||
|
### TTS_MAX_RETRIES
|
||||||
|
|
||||||
|
Maximum retries for upstream TTS failures (429/5xx).
|
||||||
|
|
||||||
|
- Default: `2`
|
||||||
|
|
||||||
|
### TTS_RETRY_INITIAL_MS
|
||||||
|
|
||||||
|
Initial retry delay in milliseconds for TTS upstream requests.
|
||||||
|
|
||||||
|
- Default: `250`
|
||||||
|
|
||||||
|
### TTS_RETRY_MAX_MS
|
||||||
|
|
||||||
|
Maximum retry delay in milliseconds.
|
||||||
|
|
||||||
|
- Default: `2000`
|
||||||
|
|
||||||
|
### TTS_RETRY_BACKOFF
|
||||||
|
|
||||||
|
Exponential backoff multiplier between retries.
|
||||||
|
|
||||||
|
- Default: `2`
|
||||||
|
|
||||||
|
### TTS_UPSTREAM_TIMEOUT_MS
|
||||||
|
|
||||||
|
Maximum upstream TTS request timeout in milliseconds.
|
||||||
|
|
||||||
|
- Default: `285000` (285 seconds)
|
||||||
|
- Applies to outbound provider calls from server routes using shared TTS generation
|
||||||
|
- Increase for slower providers/models; decrease to fail fast and surface retryable errors sooner
|
||||||
|
|
||||||
|
### TTS_ENABLE_RATE_LIMIT
|
||||||
|
|
||||||
|
Controls TTS character rate limiting in the TTS API.
|
||||||
|
|
||||||
|
- Default: `false` (TTS char limits disabled)
|
||||||
|
- Set to `true` to enforce `TTS_DAILY_LIMIT_*` and `TTS_IP_DAILY_LIMIT_*`
|
||||||
|
- For behavior details and examples, see [TTS Rate Limiting](../configure/tts-rate-limiting)
|
||||||
|
|
||||||
|
### TTS_DAILY_LIMIT_ANONYMOUS
|
||||||
|
|
||||||
|
Anonymous per-user daily character limit.
|
||||||
|
|
||||||
|
- Default: `50000`
|
||||||
|
|
||||||
|
### TTS_DAILY_LIMIT_AUTHENTICATED
|
||||||
|
|
||||||
|
Authenticated per-user daily character limit.
|
||||||
|
|
||||||
|
- Default: `500000`
|
||||||
|
|
||||||
|
### TTS_IP_DAILY_LIMIT_ANONYMOUS
|
||||||
|
|
||||||
|
Anonymous IP backstop daily character limit.
|
||||||
|
|
||||||
|
- Default: `100000`
|
||||||
|
|
||||||
|
### TTS_IP_DAILY_LIMIT_AUTHENTICATED
|
||||||
|
|
||||||
|
Authenticated IP backstop daily character limit.
|
||||||
|
|
||||||
|
- Default: `1000000`
|
||||||
|
|
||||||
|
## Auth and Identity
|
||||||
|
|
||||||
|
### BASE_URL
|
||||||
|
|
||||||
|
External base URL for this OpenReader instance.
|
||||||
|
|
||||||
|
- Required with `AUTH_SECRET` to enable auth
|
||||||
|
- Example: `http://localhost:3003` or `https://reader.example.com`
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### AUTH_SECRET
|
||||||
|
|
||||||
|
Secret key used by auth/session handling.
|
||||||
|
|
||||||
|
- Required with `BASE_URL` to enable auth
|
||||||
|
- Generate with `openssl rand -hex 32`
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### AUTH_TRUSTED_ORIGINS
|
||||||
|
|
||||||
|
Additional allowed origins for auth requests.
|
||||||
|
|
||||||
|
- Comma-separated list
|
||||||
|
- `BASE_URL` origin is always trusted automatically
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### USE_ANONYMOUS_AUTH_SESSIONS
|
||||||
|
|
||||||
|
Controls whether auth-enabled deployments can create/use anonymous sessions.
|
||||||
|
|
||||||
|
- Default: `false` (anonymous sessions disabled)
|
||||||
|
- Set `true` to allow anonymous sessions and guest-style flows
|
||||||
|
- When `false`, users must sign in or sign up with an account
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
### GITHUB_CLIENT_ID
|
||||||
|
|
||||||
|
GitHub OAuth client ID.
|
||||||
|
|
||||||
|
- Enable only with `GITHUB_CLIENT_SECRET`
|
||||||
|
|
||||||
|
### GITHUB_CLIENT_SECRET
|
||||||
|
|
||||||
|
GitHub OAuth client secret.
|
||||||
|
|
||||||
|
- Enable only with `GITHUB_CLIENT_ID`
|
||||||
|
|
||||||
|
### DISABLE_AUTH_RATE_LIMIT
|
||||||
|
|
||||||
|
Controls Better Auth rate limiting.
|
||||||
|
|
||||||
|
- Default behavior: auth-layer rate limiting enabled
|
||||||
|
- Set to `true` to disable auth-layer rate limiting
|
||||||
|
- This does not affect TTS character rate limiting
|
||||||
|
- Related docs: [Auth](../configure/auth)
|
||||||
|
|
||||||
|
## Database and Object Blob Storage
|
||||||
|
|
||||||
|
### POSTGRES_URL
|
||||||
|
|
||||||
|
Switches metadata/auth storage from SQLite to Postgres.
|
||||||
|
|
||||||
|
- Unset: SQLite at `docstore/sqlite3.db`
|
||||||
|
- Set: Postgres mode
|
||||||
|
- Related docs: [Database](../configure/database)
|
||||||
|
|
||||||
|
### USE_EMBEDDED_WEED_MINI
|
||||||
|
|
||||||
|
Controls embedded SeaweedFS startup.
|
||||||
|
|
||||||
|
- Default behavior: treated as enabled when unset
|
||||||
|
- Set `false` to rely on external S3-compatible storage
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### WEED_MINI_DIR
|
||||||
|
|
||||||
|
Data directory for embedded SeaweedFS (`weed mini`).
|
||||||
|
|
||||||
|
- Default: `docstore/seaweedfs`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### WEED_MINI_WAIT_SEC
|
||||||
|
|
||||||
|
Maximum seconds to wait for embedded SeaweedFS startup.
|
||||||
|
|
||||||
|
- Default: `20`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_ACCESS_KEY_ID
|
||||||
|
|
||||||
|
Access key for S3-compatible storage.
|
||||||
|
|
||||||
|
- Auto-generated in embedded mode if unset
|
||||||
|
- Set explicitly for stable credentials or external providers
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_SECRET_ACCESS_KEY
|
||||||
|
|
||||||
|
Secret key for S3-compatible storage.
|
||||||
|
|
||||||
|
- Auto-generated in embedded mode if unset
|
||||||
|
- Set explicitly for stable credentials or external providers
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_BUCKET
|
||||||
|
|
||||||
|
Bucket name used for document blobs.
|
||||||
|
|
||||||
|
- Default in embedded mode: `openreader-documents`
|
||||||
|
- Required for external S3-compatible storage
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_REGION
|
||||||
|
|
||||||
|
Region used by the S3 client.
|
||||||
|
|
||||||
|
- Default in embedded mode: `us-east-1`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_ENDPOINT
|
||||||
|
|
||||||
|
Endpoint URL for S3-compatible storage.
|
||||||
|
|
||||||
|
- In embedded mode, defaults to `http://<BASE_URL host>:8333` (or detected host)
|
||||||
|
- For AWS S3, usually leave unset
|
||||||
|
- For MinIO/SeaweedFS/R2/B2-style APIs, typically set explicitly
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_FORCE_PATH_STYLE
|
||||||
|
|
||||||
|
Path-style S3 addressing toggle.
|
||||||
|
|
||||||
|
- Default in embedded mode: `true`
|
||||||
|
- Set according to provider requirements
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
### S3_PREFIX
|
||||||
|
|
||||||
|
Prefix prepended to stored object keys.
|
||||||
|
|
||||||
|
- Default: `openreader`
|
||||||
|
- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
## Migration Controls
|
||||||
|
|
||||||
|
### RUN_DRIZZLE_MIGRATIONS
|
||||||
|
|
||||||
|
Controls startup migration execution in shared entrypoint.
|
||||||
|
|
||||||
|
- Default: `true`
|
||||||
|
- Set `false` to skip automatic startup Drizzle schema migrations
|
||||||
|
- Related docs: [Migrations](../configure/migrations), [Database](../configure/database)
|
||||||
|
|
||||||
|
### RUN_FS_MIGRATIONS
|
||||||
|
|
||||||
|
Controls startup filesystem-to-object-store migration execution in shared entrypoint.
|
||||||
|
|
||||||
|
- Default: `true`
|
||||||
|
- Runs `scripts/migrate-fs-v2.mjs` at startup after DB migrations
|
||||||
|
- Set `false` to skip automatic storage migration pass
|
||||||
|
- Related docs: [Migrations](../configure/migrations), [Database](../configure/database), [Object / Blob Storage](../configure/object-blob-storage)
|
||||||
|
|
||||||
|
## Library Import
|
||||||
|
|
||||||
|
### IMPORT_LIBRARY_DIR
|
||||||
|
|
||||||
|
Single directory root for server library import.
|
||||||
|
|
||||||
|
- Used when `IMPORT_LIBRARY_DIRS` is unset
|
||||||
|
- Default fallback root: `docstore/library`
|
||||||
|
- Related docs: [Server Library Import](../configure/server-library-import)
|
||||||
|
|
||||||
|
### IMPORT_LIBRARY_DIRS
|
||||||
|
|
||||||
|
Multiple library roots for server library import.
|
||||||
|
|
||||||
|
- Separator: comma, colon, or semicolon
|
||||||
|
- Takes precedence over `IMPORT_LIBRARY_DIR`
|
||||||
|
- Related docs: [Server Library Import](../configure/server-library-import)
|
||||||
|
|
||||||
|
## Audio Tooling and Alignment
|
||||||
|
|
||||||
|
### WHISPER_CPP_BIN
|
||||||
|
|
||||||
|
Absolute path to compiled `whisper.cpp` binary for word-level timestamps.
|
||||||
|
|
||||||
|
- Example: `/whisper.cpp/build/bin/whisper-cli`
|
||||||
|
- Required only for optional word-by-word highlighting
|
||||||
|
|
||||||
|
### FFMPEG_BIN
|
||||||
|
|
||||||
|
Absolute path or executable name for the ffmpeg binary used by audiobook/processing routes.
|
||||||
|
|
||||||
|
- Resolution order: `FFMPEG_BIN` -> `ffmpeg-static`
|
||||||
|
- Example: `/var/task/node_modules/ffmpeg-static/ffmpeg`
|
||||||
|
|
||||||
|
## Client Runtime and Feature Flags
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_DOCX_CONVERSION
|
||||||
|
|
||||||
|
Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
|
||||||
|
|
||||||
|
- Default: `true` (enabled)
|
||||||
|
- Set `false` to hide DOCX support in the upload UI
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS
|
||||||
|
|
||||||
|
Controls whether the "Delete all user docs" and other bulk-delete buttons are shown in Settings.
|
||||||
|
|
||||||
|
- Default: `true` (enabled)
|
||||||
|
- Set `false` to hide destructive actions (recommended for production)
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_TTS_PROVIDERS_TAB
|
||||||
|
|
||||||
|
Controls whether the **TTS Provider** section appears in the Settings modal.
|
||||||
|
|
||||||
|
- Default: `true` (enabled)
|
||||||
|
- Set `false` to hide provider/model/API controls in Settings
|
||||||
|
- Useful when you want provider config locked to environment defaults
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_DEFAULT_TTS_PROVIDER
|
||||||
|
|
||||||
|
Sets the default TTS provider for new users.
|
||||||
|
|
||||||
|
- Default: `custom-openai`
|
||||||
|
- Example values: `deepinfra`, `openai`, `custom-openai`
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_DEFAULT_TTS_MODEL
|
||||||
|
|
||||||
|
Sets the default TTS model for new users.
|
||||||
|
|
||||||
|
- Default: `kokoro`
|
||||||
|
- Example values: `hexgrad/Kokoro-82M`, `tts-1`
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
|
||||||
|
|
||||||
|
Controls whether audiobook export UI/actions are shown in the client.
|
||||||
|
|
||||||
|
- Default behavior: enabled unless explicitly set to `false`
|
||||||
|
- Applies in both development and production
|
||||||
|
- Affects export entry points in PDF/EPUB pages and document settings UI
|
||||||
|
|
||||||
|
### NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT
|
||||||
|
|
||||||
|
Controls word-by-word highlighting UI and timestamp-alignment behavior.
|
||||||
|
|
||||||
|
- Default behavior: enabled unless explicitly set to `false`
|
||||||
|
- Applies in both development and production
|
||||||
|
- Requires working timestamp generation (for example `WHISPER_CPP_BIN`)
|
||||||
|
- Affects:
|
||||||
|
- Word-highlight toggles in document settings
|
||||||
|
- Alignment requests during TTS playback
|
||||||
44
docs-site/versioned_docs/version-v2.1.1/reference/stack.md
Normal file
44
docs-site/versioned_docs/version-v2.1.1/reference/stack.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
title: Stack
|
||||||
|
---
|
||||||
|
|
||||||
|
## Framework
|
||||||
|
|
||||||
|
- [Next.js](https://nextjs.org/) 15 (App Router)
|
||||||
|
- [React](https://react.dev/) 19
|
||||||
|
- [TypeScript](https://www.typescriptlang.org/)
|
||||||
|
|
||||||
|
## 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`
|
||||||
|
- Authentication: [Better Auth](https://www.better-auth.com/) client SDK
|
||||||
|
- Local storage/cache: [Dexie.js](https://dexie.org/) (IndexedDB)
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- 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
|
||||||
|
- Audio/processing pipeline: OpenAI-compatible TTS providers, [ffmpeg](https://ffmpeg.org/) for audiobook assembly, optional [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for word timestamps
|
||||||
|
|
||||||
|
## Tooling and testing
|
||||||
|
|
||||||
|
- ESLint
|
||||||
|
- TypeScript
|
||||||
|
- [Playwright](https://playwright.dev/) end-to-end tests
|
||||||
|
- Drizzle migration/generation scripts
|
||||||
|
|
@ -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)
|
||||||
|
- [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
|
||||||
|
- [ffmpeg](https://ffmpeg.org)
|
||||||
|
- [react-pdf](https://github.com/wojtekmaj/react-pdf)
|
||||||
|
- [react-reader](https://github.com/happyr/react-reader)
|
||||||
7
docs-site/versioned_docs/version-v2.1.2/about/license.md
Normal file
7
docs-site/versioned_docs/version-v2.1.2/about/license.md
Normal file
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
46
docs-site/versioned_docs/version-v2.1.2/configure/auth.md
Normal file
46
docs-site/versioned_docs/version-v2.1.2/configure/auth.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
title: Auth
|
||||||
|
---
|
||||||
|
|
||||||
|
This page covers application-level configuration for provider access and authentication.
|
||||||
|
|
||||||
|
## Auth behavior
|
||||||
|
|
||||||
|
- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
|
||||||
|
- Remove either value to disable auth.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Route behavior
|
||||||
|
|
||||||
|
- `/` is a public landing/onboarding page and remains indexable.
|
||||||
|
- `/app` is the protected app home (document list and uploader UI).
|
||||||
|
- If auth is enabled and a valid session exists (including anonymous), visiting `/` redirects to `/app`.
|
||||||
|
- 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 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.
|
||||||
|
|
||||||
|
### Auth disabled
|
||||||
|
|
||||||
|
- Settings and reading progress stay local in the browser (Dexie/IndexedDB).
|
||||||
|
- This avoids no-auth cross-browser conflicts, but there is no cross-device sync.
|
||||||
|
|
||||||
|
## Claim modal note
|
||||||
|
|
||||||
|
- You may still see old anonymous settings/progress available to claim from older deployments.
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
title: Database
|
||||||
|
---
|
||||||
|
|
||||||
|
This page covers database mode selection for OpenReader.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
|
||||||
|
- With auth disabled, settings and reading progress remain local in the browser.
|
||||||
|
- Sync is currently request-based (not realtime push invalidation).
|
||||||
132
docs-site/versioned_docs/version-v2.1.2/configure/migrations.md
Normal file
132
docs-site/versioned_docs/version-v2.1.2/configure/migrations.md
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
<Tabs groupId="apply-migration-commands">
|
||||||
|
<TabItem value="project-scripts" label="Project Scripts" default>
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<Tabs groupId="generate-migration-commands">
|
||||||
|
<TabItem value="project-script" label="Project Script" default>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full pipeline: Better Auth CLI + Drizzle generate (both dialects)
|
||||||
|
pnpm generate
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="drizzle-direct" label="Manual Drizzle Cmd">
|
||||||
|
|
||||||
|
```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.
|
||||||
|
:::
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- [Database](./database)
|
||||||
|
- [Object / Blob Storage](./object-blob-storage)
|
||||||
|
- [Migration Environment Variables](../reference/environment-variables#migration-controls)
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue