Repository navigation
Expand file tree
/
Copy path.env.example
More file actions
449 lines (400 loc) · 24.8 KB
/
Copy path.env.example
File metadata and controls
449 lines (400 loc) · 24.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# Server encryption key for API key storage (generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))").
# Required in production. Outside production it is optional: when unset, a key is
# auto-generated and written to a file named .encryption-key next to the SQLite
# database (not inside it), with 0600 permissions. Precedence: this env var, then
# that key file, then a legacy key found in the old settings table (migrated to
# the file on first boot), then a freshly generated key.
ENCRYPTION_KEY=your-64-char-hex-key-here
# Server port (default: 3001)
PORT=3001
# Network interface the server listens on. Default '::' (dual-stack IPv4+IPv6);
# hosts with IPv6 disabled fall back to IPv4 automatically. Set 0.0.0.0 for
# IPv4-only or 127.0.0.1 to restrict to localhost. (Different from HOST_BIND
# below, which only affects Docker port publishing.)
# HOST=::
# First-run setup: the first account is created through the dashboard. A browser
# on the same machine as the server can do this with no extra step. If the server
# is reachable from other devices, creating that first account also requires a
# one-time setup code, printed in the server logs at startup while no account
# exists. This stops a stranger from claiming a freshly exposed install.
# Docker only: which host interface the container's port is published on.
# Defaults to 127.0.0.1, so the dashboard/API is reachable only from the
# machine running Docker. To open it to other devices on your LAN (e.g. a
# Raspberry Pi reached at http://192.168.1.x:3001), set HOST_BIND=0.0.0.0.
# Only do this on a trusted network — the proxy is single-user and guarded
# only by the unified API key.
# HOST_BIND=0.0.0.0
# Max /v1 proxy requests per minute per client IP (default: 120). Set to 0
# to disable proxy rate limiting.
# PROXY_RATE_LIMIT_RPM=120
# SSE heartbeat interval in ms for streaming requests (#1642). Reasoning
# models can go minutes without a first token, and clients with an idle
# watchdog (Claude Code) abort during that silence. Set to e.g. 15000 and
# each waiting stream emits a content-free SSE ping every 15s so the client
# keeps waiting. OFF by default: a ping commits the response, so a stream
# that dies after one ends with an error frame instead of failing over to
# the next provider invisibly. Floor 1000 ms.
# STREAM_KEEPALIVE_MS=15000
# Max /api dashboard requests per minute per client IP (default: 600). This is a
# flood guard — login has its own per-email lockout and key export its own much
# tighter cap. Set to 0 to disable.
# ADMIN_RATE_LIMIT_RPM=600
# JSON body limit in MB for the inference surfaces (/v1, /v1beta, /mcp,
# Ollama /api/*) — default: 25. Vision requests embed base64 images in the
# body and replay them with every turn, so large sessions can clear 10MB;
# this ceiling only gates parsing — inbound image normalization (below)
# shrinks the payload afterwards. Raise it if bigger payloads are rejected
# with 413 'request_too_large'.
# REQUEST_BODY_LIMIT_MB=25
# ChatGPT / MCP inference. ask_freellmapi uses "auto" unless this is set to
# a model id or named chain (for example auto:coding). The timeout applies only
# to the MCP inference tool; normal /v1 client requests keep their own limits.
# MCP_INFERENCE_DEFAULT_MODEL=auto
# MCP_INFERENCE_TIMEOUT_MS=120000
# Inbound image normalization: data-URL images over the threshold are
# downscaled to the long-edge cap and re-encoded (JPEG, or PNG only when the
# alpha channel carries real transparency) before routing, shrinking replayed
# screenshots ~6-10x. Also normalizes exotic formats upstreams reject
# (bmp/gif/tiff/avif/webp → jpeg/png); gif keeps its first frame only. Every
# upstream resizes internally anyway (OpenAI to 2048px, Anthropic to 1568px),
# so pixels beyond the cap are transport waste. Set IMAGE_NORMALIZE=off to
# disable entirely.
# IMAGE_NORMALIZE=on
# IMAGE_NORMALIZE_MAX_DIMENSION=2048
# IMAGE_NORMALIZE_THRESHOLD_KB=1024
# IMAGE_NORMALIZE_QUALITY=90
# Outbound proxy for provider requests. Normally set in the dashboard
# (Keys → Outbound proxy); these env vars are for headless installs.
# Schemes: http, https, socks4, socks4a, socks5, socks5h. The `h`/`a` variants
# resolve DNS at the proxy, which is what you want on a DNS-poisoned network.
# Precedence: PROXY_URL → dashboard setting → ALL_PROXY → HTTPS_PROXY →
# HTTP_PROXY. The standard vars are also read in their lower-case spellings.
# PROXY_URL=socks5h://127.0.0.1:1080
# ALL_PROXY=socks5h://127.0.0.1:1080
# HTTPS_PROXY=http://proxy.corp.com:8080
# HTTP_PROXY=http://proxy.corp.com:8080
#
# Application-layer Fetch Relay (opt-in). The relay target and its separate
# bearer credential use Fetch-Relay-Target and Fetch-Relay-Authorization.
# See docs/en/proxy/01-fetch-relay.md.
# PROXY_MODE=fetch-relay
# PROXY_URL=https://relay.example.workers.dev
# FETCH_RELAY_TOKEN=generate-a-long-random-token
#
# IN DOCKER, 127.0.0.1 IS THE CONTAINER, NOT YOUR MACHINE. If the proxy runs
# on the host (Clash, v2rayN, sing-box, a corporate client), use the host's
# address instead, and make sure the proxy listens on more than loopback
# (Clash: `allow-lan: true`):
# PROXY_URL=socks5h://host.docker.internal:7890
# The bundled docker-compose.yml maps host.docker.internal to the host gateway
# so this works on plain Linux Docker too, not just Docker Desktop.
# Hosts that must be reached directly, bypassing the proxy above. Comma-
# separated; a bare domain also covers its subdomains, and `*` disables the
# proxy entirely.
# NO_PROXY=localhost,127.0.0.1,.internal.corp
# Optional fail-closed outbound policy. These platform IDs MUST use a proxy:
# if the proxy is missing, disabled, bypassed, or its agent cannot be built,
# return an explicit error instead of silently connecting directly.
# Non-listed platforms keep the normal routing behavior. This controls NETWORK
# transport only; it does not disable model/provider failover in auto routing.
# Example: require Gemini to use your configured SOCKS5 tunnel.
# PROXY_REQUIRED_PLATFORMS=google
# Local and LAN destinations (localhost, 127.0.0.0/8, ::1, 0.0.0.0, RFC1918 /
# ULA / CGNAT addresses) always bypass the proxy — a remote proxy has no route
# back to your own machine, and an IP literal makes Tor log "giving Tor only an
# IP address" and refuse the connection. That is the right default for a local
# Ollama / llama.cpp / LM Studio endpoint.
# Set this to true only if proxying them is the point — e.g. an `ssh -D`
# dynamic tunnel, where http://127.0.0.1:11434 through the SOCKS proxy is meant
# to reach the REMOTE host's Ollama.
# FREEAPI_PROXY_LOCAL_DESTINATIONS=true
# Per-provider daily request cap, named PROVIDER_DAILY_REQUEST_CAP_<PLATFORM>
# (platform name upper-cased). Overrides the built-in default for that provider;
# set to 0 to disable the cap. Example — cap OpenRouter at 50 requests/day:
# PROVIDER_DAILY_REQUEST_CAP_OPENROUTER=50
# Per-provider chat HTTP timeout in milliseconds, named
# PROVIDER_TIMEOUT_<PLATFORM> (platform name upper-cased). Overrides the
# built-in default for that provider (60s for most OpenAI-compatible platforms,
# 180s NVIDIA, 120s Ollama Cloud / AI Horde / custom providers, 60s Google as
# registered). On streaming requests this value also serves as the FIRST-BYTE
# grace budget: how long the stream may stay silent before its first byte
# (issue #584 — providers like NVIDIA NIM send SSE headers instantly, then
# prefill long prompts for minutes). Set to 0 to disable the timeout entirely.
# Read at startup. Example — give NVIDIA NIM's slow reasoning models 5 minutes:
# PROVIDER_TIMEOUT_NVIDIA=300000
# ModelScope (魔搭) validates API keys with a paid 1-token chat completion
# (GET /v1/models does not enforce auth), which spends the account's
# magic-grain (魔粒) quota per probe — observed as 2 魔粒 per ultra-tier
# request. To stop the 5-minute health pass from burning ~288 paid probes
# per key per day, a successful validation is cached per key for this long
# (milliseconds, default 86400000 = 24h). Set to 0 to probe on every pass.
# A revoked key is still caught by the next real request's 401.
# MODELSCOPE_VALIDATE_CACHE_MS=86400000
# Mid-stream inactivity timeout for streaming responses, in milliseconds
# (default: 90000). If an SSE stream goes this long without a single byte
# AFTER the first byte arrived, the gateway gives up on it (before any output
# reached the client this fails over to the next candidate; after output was
# sent the stream ends with an error frame). Time-to-FIRST-byte is governed by
# the chat timeout above instead (whichever of the two is larger). Providers
# that queue heavily on their free tiers can pause longer than 90s mid-stream —
# raise this if streamed responses stop early. Set to 0 to disable the stall
# watchdog entirely.
# PROVIDER_STREAM_STALL_TIMEOUT_MS=90000
#
# Per-provider variant, named PROVIDER_STREAM_STALL_TIMEOUT_<PLATFORM>
# (platform name upper-cased). Precedence: per-platform > global (above) >
# built-in 90s default. Example — let Ollama Cloud pause 4 minutes mid-stream
# without loosening the watchdog everywhere else:
# PROVIDER_STREAM_STALL_TIMEOUT_OLLAMA=240000
# Base cadence for the scheduled key health pass, in milliseconds (default:
# 300000 = 5m, minimum 60000; lower or invalid values fall back to the default).
# Each pass applies ±20% jitter. A faster cadence sends more validation traffic,
# which costs quota where a provider's key check is metered. Read at startup.
# HEALTH_CHECK_INTERVAL_MS=300000
# Optional destination for soft budget alerts: when a provider key crosses 80%
# or 100% of its monthly request/token cap, the alert is POSTed to this URL as
# JSON (type: freellmapi.key_budget_threshold) in addition to the Logs page
# event. http(s) only; delivery is fire-and-forget with a 10s timeout. Works
# with Slack/Discord via a workflow webhook. Read when an alert fires.
# BUDGET_ALERT_WEBHOOK_URL=https://hooks.example.test/abc
# Opt-in background model probes (#1438): every 30 minutes, send a tiny
# "Reply with OK" chat to up to MODEL_PROBE_MAX_PER_PASS (default 3) model+key
# pairs and record whether each model answers. Off by default because every
# probe spends free-tier quota. POST /api/health/probe-models runs one pass on
# demand either way.
# MODEL_PROBE_ENABLED=1
# MODEL_PROBE_MAX_PER_PASS=3
# Base wall-clock budget for provider failover, in milliseconds (default: 45000).
# The initial attempt and one failover hop are always allowed. After that,
# exhaustion stops new attempts and can cancel an in-flight attempt still
# waiting for its first usable response. Streaming responses disarm this timer
# when the first usable response arrives. Successful TTFB history can widen
# the budget for a slow endpoint (see below). Set to 0 to disable the budget
# entirely, including adaptive widening, and retry until the attempt cap.
# Can also be set at runtime via the fallback_time_budget_ms settings key,
# which takes precedence.
# FALLBACK_TIME_BUDGET_MS=45000
# Adaptive patience for slow endpoints: successful time-to-first-byte (TTFB)
# samples are grouped by platform + endpoint_scope. The effective budget is
# max(base budget, historical P95 TTFB + this buffer), in milliseconds.
# Endpoints with no samples, and those whose P95 + buffer fits the base budget,
# keep the base budget. The settings key slow_endpoint_buffer_ms takes
# precedence over this env var. Set the buffer to 0 for no extra padding.
# SLOW_ENDPOINT_BUFFER_MS=10000
# Successful TTFB sample window, in milliseconds (default: 7 days).
# The settings key ttfb_budget_window_ms takes precedence.
# TTFB_BUDGET_WINDOW_MS=604800000
# Sample-weight half-life, in milliseconds (default: 2 days). Older successes
# count less so the budget adapts as an endpoint speeds up or slows down.
# The settings key ttfb_budget_half_life_ms takes precedence.
# TTFB_BUDGET_HALF_LIFE_MS=172800000
# Successful samples an endpoint needs before its history widens the budget
# (default: 5). The widened budget is capped at three times the base budget.
# The settings key ttfb_budget_min_samples takes precedence.
# TTFB_BUDGET_MIN_SAMPLES=5
# Set to 1 to disable adaptive TTFB budgets and use only the base budget.
# TTFB_BUDGET_DISABLED=1
# Add X-Fallback-Detail to responses (default: off). X-Fallback-Trail already
# names every hop that failed and why; the detail header adds what each one
# COST — "groq/llama-3.3-70b key1=rate_limited t=0+39000ms msg=..." — so a
# caller can tell one 39s stall apart from four fast failures without opening
# the dashboard. Off by default because it puts hop timings and provider error
# text on the response. Can also be set at runtime via the
# expose_fallback_detail_header settings key, which takes precedence.
# FALLBACK_DETAIL_HEADER=1
# Check tool-call arguments against the schema the caller declared, and fail
# over to another model when they violate it (default: off). lib/tool-args.ts
# already repairs what it can prove is repairable; this catches what is left,
# which today reaches Anthropic clients as a tool_use block with `input: {}` and
# no indication anything went wrong. A different model usually gets the same
# call right. Off by default because a false positive costs a failover hop —
# turn it on, watch X-Fallback-Trail for `invalid_tool_arguments`, then decide.
# Applies wherever the turn can still fail over — every non-streaming surface,
# and the streams that buffer tool calls to the end (/v1/chat/completions,
# /v1/messages, the Gemini and Ollama emulations). /v1/responses streaming is
# the exception: it commits on the first tool-call delta, before the arguments
# are complete, so a verdict there could only break a stream in flight.
# Can also be set at runtime via the validate_tool_arguments settings key,
# which takes precedence.
# VALIDATE_TOOL_ARGUMENTS=1
# Request guardrails, both OFF (0) by default. Also runtime-tunable via the
# request_max_tokens_budget / max_consecutive_upstream_fails settings keys
# (dashboard API PUT /api/settings/guardrails), which take precedence.
#
# Per-request token budget: estimated input + requested max_tokens must fit
# this ceiling or the request is rejected with a 413 before any provider is
# tried; a request that sent no max_tokens gets its output capped to the
# remainder instead.
# REQUEST_MAX_TOKENS_BUDGET=0
#
# Failover circuit breaker: after this many consecutive upstream failures in
# one request, stop the failover chain with a 503 instead of trying every
# remaining candidate of an unhealthy pool.
# MAX_CONSECUTIVE_UPSTREAM_FAILS=0
# Opt-in response cache. When on, a successful /v1/chat/completions answer --
# streaming or not -- is stored in a bounded in-memory LRU keyed by a hash of the
# request, so an IDENTICAL later request is served from memory without spending
# any provider quota. Exact match only (no fuzzy matching), so a near-miss never
# returns a different prompt's answer. Off by default; set to true to enable.
# Can also be toggled at runtime via the dashboard (PUT /api/cache/config), and
# per-request with the `X-FreeLLM-Cache: on|off` header.
# RESPONSE_CACHE=false
# Persist cached non-streaming answers to SQLite so they survive a restart
# (streamed replays are memory-only and never persisted) (the daily
# quota-reset re-run pattern). Only applies when the cache above is on. Note
# this writes PLAINTEXT model responses to the database file, which the
# memory-only cache never did; set it to false to keep caching but hold
# everything in memory, as before. Entries are removed from disk the moment
# they leave the in-memory cache (TTL expiry, eviction, DELETE /api/cache).
# RESPONSE_CACHE_PERSIST=true
# How long a cached answer stays fresh, in seconds (default: 3600 = 1 hour).
# RESPONSE_CACHE_TTL_SECONDS=3600
# Only cache requests at/below this temperature; higher temperatures want fresh
# variety, so they are never cached. Default 1.0 caches everything when enabled;
# lower it (e.g. 0.2) to cache only near-deterministic calls.
# RESPONSE_CACHE_MAX_TEMPERATURE=1.0
# Hard cap on stored entries, shared by the JSON and streaming stores; the
# least-recently-used are evicted past this.
# RESPONSE_CACHE_MAX_ENTRIES=5000
# Request-side prompt/context compression. The default is off. `lossless`
# applies whitespace hygiene, exact repeated-block references, and reversible
# tabular-JSON encoding; `standard` also filters tool output and superseded file
# reads; `aggressive` adds age/relevance/budget condensation. The dashboard can
# change this live under Settings. A request may lower the configured mode with
# `X-FreeLLM-Compress`, but cannot override a global `off` master switch.
# FREELLMAPI_COMPRESSION=off
# Custom-provider URL policy. Cloud metadata and link-local addresses
# (169.254.169.254, metadata.google.internal, fe80::/10, ...) are ALWAYS
# blocked as custom provider base URLs. By default localhost and private LAN
# addresses stay allowed so local Ollama / LM Studio / llama.cpp setups work.
# If you host FreeLLMAPI on a VPS or anywhere the dashboard is reachable by
# others, set this to also block loopback and RFC1918/ULA private ranges.
# FREEAPI_BLOCK_PRIVATE_PROVIDER_URLS=true
# Request analytics retention. Set either value to 0 to disable that limit.
REQUEST_ANALYTICS_RETENTION_DAYS=90
REQUEST_ANALYTICS_MAX_ROWS=100000
# Provider quota observation log (the audit trail behind the per-key quota
# state the dashboard shows). Only the newest row per pool is ever read, so the
# history is pruned daily by age and by count. 0 disables that half.
# QUOTA_OBSERVATIONS_RETENTION_DAYS=7
# QUOTA_OBSERVATIONS_MAX_ROWS=50000
# Persisted server logs behind the dashboard's log viewer. Only warn/error
# lines are written to the database (the live view is an in-memory ring), so
# these bounds are far tighter than the analytics ones above. Either value at 0
# disables that limit.
# SERVER_LOGS_RETENTION_DAYS=7
# SERVER_LOGS_MAX_ROWS=50000
# Web-search attempt log behind the dashboard's Search page (search_requests).
# Every search attempt appends a row (an agent web_search can issue up to 20),
# so the log is bounded by age and count on the same pass as the analytics
# prune. 0 disables that half.
# SEARCH_LOGS_RETENTION_DAYS=90
# SEARCH_LOGS_MAX_ROWS=100000
# Decision-model attempt log behind the dashboard's Models → Decision page
# (decision_requests). Every /v1/decisions attempt appends a row, so the log
# is bounded by age and count on the same pass as the analytics prune.
# 0 disables that half.
# DECISION_LOGS_RETENTION_DAYS=90
# DECISION_LOGS_MAX_ROWS=100000
# Per-request caller identity (client IP + User-Agent) recorded into request
# analytics and shown in the dashboard's "Recent calls" table. Set to false
# to store nulls instead (aggregate analytics are unaffected).
# REQUEST_ANALYTICS_LOG_CLIENT=true
# Optional SQLite location. Useful on hosts where only one directory is mounted
# persistently, or when you want to keep the DB outside server/data.
# FREEAPI_DB_PATH=/app/server/data/freellmapi.db
# Whether to restrict the directory holding the database to this account, so the
# SQLite -wal/-shm sidecars are protected too (they are created on first write,
# after any startup permission check could reach them). Done automatically for
# the default server/data directory and for any directory FreeLLMAPI creates
# itself; skipped for a FREEAPI_DB_PATH pointing somewhere pre-existing, since
# that may be a shared location this server should not be locking down. Set to 1
# if the directory belongs to this account alone, or 0 to disable entirely.
# FREEAPI_DB_DIR_HARDENING=1
# Optional encrypted SQLite backup. On startup, FreeLLMAPI restores this backup
# if the configured DB file is missing; while running, it uploads a fresh backup
# periodically. File paths and HTTP(S) URLs are supported.
# FREEAPI_DB_BACKUP_PATH=/app/server/data/freellmapi.db.backup
# FREEAPI_DB_BACKUP_URL=https://example.com/freellmapi.db.backup
# FREEAPI_DB_BACKUP_TOKEN=
# FREEAPI_DB_BACKUP_KEY=use-a-separate-64-char-hex-key-or-omit-to-use-ENCRYPTION_KEY
# FREEAPI_DB_BACKUP_INTERVAL_MS=300000
# Automatic dump files on the Backups page (#1675): how many dumps created by
# the scheduler or the desktop updater's pre-update snapshot to keep. Older
# ones are deleted (file + row) on the next dump write. Manual and pre-restore
# dumps are never auto-deleted. 0 keeps every auto dump (disables the prune).
# AUTO_BACKUP_DUMP_RETENTION=10
# Optional declarative startup config. Use either inline JSON or a path to a JSON
# file. It is applied idempotently after migrations on every boot — before the
# HTTP listener starts, so an "admin" entry closes the first-run setup window
# entirely. Optional "admin" creates the first dashboard account (only while no
# account exists); optional "license" activates a freellmapi.co Premium key.
# The JSON carries plaintext secrets — protect it like ENCRYPTION_KEY.
# FREEAPI_CONFIG_PATH=/app/server/data/freellmapi.config.json
# FREEAPI_CONFIG_JSON={"admin":{"email":"ops@example.com","password":"change-me"},"license":"...","keys":[{"platform":"groq","key":"gsk_...","label":"main"}],"routing":{"strategy":"balanced"}}
# Context handoff on model switch. When enabled, FreeLLMAPI injects a compact
# system message into the outbound request whenever a session switches from one
# model to another (e.g. after a fallback). Off by default.
# FREELLMAPI_CONTEXT_HANDOFF=on_model_switch
# Manual application update checker. Set to off to hide it from Settings and
# prevent Git discovery or outbound update-check requests. Enabled by default.
# This also switches off the automatic release reminder, which is a separate
# dashboard setting (Settings > General) and is off until turned on there.
# FREELLMAPI_UPDATE_CHECK=off
# Optional token used only for update checks against GitHub. The checker is
# anonymous by default; use a narrowly scoped token only if higher rate limits
# are needed. Generic GITHUB_TOKEN values are intentionally ignored.
# FREELLMAPI_UPDATE_GITHUB_TOKEN=
# FREELLMAPI_COMMIT_SHA and FREELLMAPI_INSTALL_METHOD are injected by official
# Docker and desktop builds and normally should not be set in .env.
# Docker "Update now" (Settings > Check for updates). Set a long random token,
# e.g. `openssl rand -hex 32`, then start with
# `docker compose --profile autoupdate up -d`. See docker-compose.yml.
# WATCHTOWER_TOKEN=
# Comma-separated extra origins allowed to call the API from a browser.
# localhost:5173, 127.0.0.1:5173, [::1]:5173 are allowed by default for the
# Vite dev dashboard. Only set this if you serve the dashboard from a
# different host than the API (e.g. http://my-server.local).
# DASHBOARD_ORIGINS=http://my-server.local,http://192.168.1.50
# Content-Security-Policy `upgrade-insecure-requests` directive (#682).
# Auto (unset): emit only when the request arrives over TLS or behind an
# HTTPS reverse proxy (X-Forwarded-Proto: https). Plain-HTTP LAN installs
# (HOST_BIND=0.0.0.0, no TLS in front) stay renderable.
# true: force the directive on even over plain HTTP (rarely useful).
# false: force it off even behind HTTPS (e.g. mixed-content reverse proxies).
# CSP_UPGRADE_INSECURE_REQUESTS=false
# Trust forwarded headers (X-Forwarded-For / X-Forwarded-Proto) from a reverse
# proxy so Analytics and IP-based rate limiting see the real client (#1024).
# Default (unset or false): do NOT trust forwarded headers — direct callers
# cannot spoof the client IP.
# true: trust every proxy hop (only use on a fully trusted network).
# A number: trust that many hops in front of the server (TRUST_PROXY=1 for a
# single reverse proxy such as Caddy, nginx or Traefik on the same host).
# Otherwise: a comma-separated list of proxy addresses/CIDRs to trust, e.g.
# TRUST_PROXY=100.64.0.0/10,192.168.1.10
# TRUST_PROXY=false
# Host-header allowlist against DNS rebinding (#1637). Off by default. Once
# set, a request whose Host header names anything other than loopback
# (localhost, 127.0.0.1, ::1, *.localhost) or a listed hostname gets 403
# before auth or routing. List every hostname/IP you reach the server by,
# comma-separated. Loopback-only:
# ALLOWED_HOSTS=localhost
# LAN or reverse proxy:
# ALLOWED_HOSTS=192.168.1.50,freellmapi.home.arpa
# Unset (or *) leaves the check off.
# ALLOWED_HOSTS=
# Per-model routing score multipliers (#738), JSON object of model id → 0..2.
# 1.0 leaves a model unchanged, below 1 demotes it, above 1 promotes it, and 0
# means the automatic router never picks it (Manual priority still can).
# Matching is by model id alone, across all platforms serving that id, and the
# match is exact and case-sensitive. Copy ids from the dashboard Models page:
# an id that matches nothing is silently ignored (the boot log flags it once the
# catalog has synced).
# MODEL_ROUTING_OVERRIDES={"gpt-4o": 0.2, "gpt-oss-120b": 1.5}
# --- Advanced / embedding ---------------------------------------------------
# Path to a prebuilt client `dist` directory to serve. Defaults to the bundled
# client build; set this only if you build the dashboard separately.
# CLIENT_DIST=/path/to/client/dist
# Explicit path to the .env file to load. Useful for embedders (e.g. the
# desktop app, where the code runs from inside a bundle). Defaults to ./.env.
# FREEAPI_ENV_PATH=/path/to/.env