Configuration
The server's settings live in two places: tenants.json lists the people it serves, and environment variables (usually in .env) set everything else. When a required setting is missing, the server refuses to start and writes the missing ones to its log.
Version
This page follows the main branch. v0.1.0 has no UI_LANG and no lang field in tenants.json; up to v0.2.0, LOOP_STALL_MS is off by default. Everything else is the same.
tenants.json
A JSON array with one entry per person (tenant). Its permissions must be 600.
[{"id": "main", "token": "<token>", "data": "/data/main", "recordings": "/data/main/recordings"}]| Field | Description |
|---|---|
id | The tenant name, used in logs and metrics |
token | The token the app and the web pages send (Authorization: Bearer or a cookie) |
data | The data directory: live.db and more, see Data layout |
recordings | The recordings directory |
lang | Optional. The language the LLM writes this person's content in (summaries, to-dos, live analysis, the daily recap, "ask AI" answers and more): zh or en. Without it, UI_LANG applies, and without UI_LANG, zh. Once the language is switched on the web page 「我的」 (Me) (PUT /api/lang), that choice wins: it is kept in this person's database and survives a restart |
- Location:
DATA_DIR/tenants.json(seeDATA_DIRbelow). In the Docker imageDATA_DIRis/data, which makes itdata/tenants.jsonon the host. - When the file is missing, the server's first start makes one: one tenant
mainwith a random token and the directoriesDATA_DIR/mainandDATA_DIR/main/recordings, and logs the link that signs you in,http://127.0.0.1:<PORT>/connect?token=<token>. id,token,dataandrecordingsmust each be unique across tenants.- With a single tenant the token can be empty. The server then skips authentication and must stay on
127.0.0.1. With several tenants, each one needs a token. - The server reads this file only at startup.
.env
.env.example lists every variable below, each with a comment. "Example value" is the value in .env.example, your starting point once you copy it to .env; "When unset" is the default in the code when the variable doesn't exist at all.
Put each comment in .env on a line of its own: Docker Compose reads VAR= # comment as the value # comment.
Required: ASR
| Variable | Example value | When unset | Description |
|---|---|---|---|
ENGINE | qwen | qwen | The ASR engine of the final layer: qwen, volc, stepfun-sse, stepfun, stepfun-file, local. See Change the ASR engine |
DASHSCOPE_API_KEY | empty | — | Alibaba Cloud Bailian (DashScope) key, Beijing region. Used by qwen and qwen-stream |
Required: LLM
Used for conversation summaries, to-dos, "ask AI" and the daily recap. Any OpenAI-compatible POST /chat/completions endpoint that supports tool calls and response_format json_object works: a cloud service, or one you run yourself (vLLM, llama.cpp, Ollama).
| Variable | Example value | When unset | Description |
|---|---|---|---|
LLM_BASE_URL | https://dashscope.aliyuncs.com/compatible-mode/v1 | required | The endpoint; the server appends /chat/completions |
LLM_API_KEY | empty | required | The endpoint's key; with the example's Bailian compatible mode it is the same key as DASHSCOPE_API_KEY |
LLM_MODEL | qwen-plus | required | The model name |
Optional
| Variable | Example value | When unset | Description |
|---|---|---|---|
DATA_DIR | empty | the user data directory: ~/.local/share/alldayrec on Linux (alldayrec under XDG_DATA_HOME when that is set), ~/Library/Application Support/alldayrec on macOS | The directory tenants.json is in; without MODELS_DIR the model files go in its models, and the tenant a first start makes in its main. Fixed at /data in Docker |
MODELS_DIR | empty | DATA_DIR/models | The model files directory, an absolute, writable path; missing files are downloaded here at startup. Fixed at /data/models in Docker |
PORT | 8770 | 8770 | The port to listen on. Docker Compose fixes it at 8770 |
HOST | 127.0.0.1 | 127.0.0.1 | The address to listen on; set 0.0.0.0 when the reverse proxy runs on another machine. Docker Compose fixes it at 0.0.0.0 |
DRAFT_ENGINE | empty | same as ENGINE | The engine of the draft layer (while a page is open); it can also be qwen-stream: while a page is open, the audio streams to Qwen (Bailian qwen-audio-3.1-asr-flash-streaming, with DASHSCOPE_API_KEY), so words show as they are said |
ENGINE_FALLBACK | empty | none | The engine used when ENGINE or DRAFT_ENGINE fails on a window (402, 451, 5xx, timeout); empty means a retry later |
VOLC_ASR_KEY | empty | — | The API key of a Volcengine Ark Agent Plan, used by volc |
STEPFUN_API_KEY | empty | — | StepFun key, used by stepfun and stepfun-sse |
STEPFUN_SSE_URL | empty | https://api.stepfun.com/v1/audio/asr/sse | The StepFun SSE endpoint; the default is the pay-as-you-go address; for Step Plan use .../step_plan/v1/audio/asr/sse |
LOCAL_ASR_URL | empty | the k2-fsa/sherpa-onnx GitHub Release | Where local downloads its model tarball (about 136 MB) from; the same tarball on a mirror works too (sha256-checked) |
UI_LANG | zh | zh | The language of what the LLM writes (summaries, to-dos, live analysis, the daily recap, "ask AI" answers and more): zh or en. A lang in tenants.json wins. The web pages use it too in a browser that hasn't picked a language; the app's UI doesn't change |
RECAP_AT | 10:00 | 10:00 | The time of day the server writes yesterday's recap; set it empty for no recap |
TZ | Asia/Shanghai | the system time zone | The time zone for conversation dates and RECAP_AT. A Docker container's system time zone is UTC; an empty TZ= means UTC too |
REFINE_EVERY_S | 20 | 30 | Draft layer: run once after this many seconds without a pause |
TURN_GAP_S | 0 | 1.0 | Above 0, joins adjacent sentences of the same speaker less than this many seconds apart into one line; 0 keeps the engine's own sentence split |
FIRMWARE_TOKEN | empty | none | The token needed, besides the tenant token, to upload recorder firmware (firmware/devkit/lr_release.py); empty means no uploads |
VM_INGEST_URL | empty | none | A VictoriaMetrics import URL (e.g. https://example.com/api/v1/import/prometheus); the server pushes lr_* metrics to it every minute; empty means no push |
VM_INGEST_USER, VM_INGEST_PASSWORD | empty | empty | Basic auth for pushing metrics |
Advanced: only for stepfun and stepfun-file
When stepfun or stepfun-file appears in ENGINE, DRAFT_ENGINE or ENGINE_FALLBACK, all four ASR_* variables below are required.
| Variable | Example value | When unset | Description |
|---|---|---|---|
ASR_UPLOAD_BRIDGE_URL | empty | — | Your own upload bridge: POST {filename, content_type}, returns {upload_url, download_url} |
ASR_UPLOAD_BRIDGE_TOKEN | empty | — | The upload bridge's token |
ASR_SERVICE_URL | empty | — | Your own file-mode ASR service (with speakers and word times): POST /audio/transcriptions {audio_url, model}, GET /jobs/{id} |
ASR_SERVICE_BASIC_AUTH | empty | — | That service's basic auth, written as user:password |
FILE_MODELS | stepaudio-2.5-asr-8006,doubao-seedasr-2.0 | same as the example value | The file-mode engines, in order; a window the first one rejects goes to the second |
Only for offline scripts
| Variable | Used in | Description |
|---|---|---|
LIVE_DATA, RECORDINGS_DIR | owner.py, replay_case.py | A tenant's data directory and recordings directory |
OWNER_LABELS | owner.py | Windows known to be your own voice (jsonl) |
CASES_DIR | eval_all.sh | The directory of labeled evaluation cases |
EMB_MODEL, SEG_MODEL, OWNER_SURE, LINK_MIN, OWNER_DEFAULT_DEVICE | replay_case.py, service_case.py | See those two scripts |
Other environment variables
The server also reads the variables below, which .env.example doesn't list. They are for tuning, and their defaults are the values the author uses.
| Variable | When unset | Description | Source |
|---|---|---|---|
SLICE_FOREGROUND_S, SLICE_BACKGROUND_S, SLICE_SCREEN_OFF_S | 1, 20, 60 | Seconds of audio per piece while the app is in the foreground, in the background, or with the screen off (Android); the app reads them from /api/client-config | routes_upload.py |
REFINE_WINDOW_S | 240 | The most seconds of audio the draft layer handles at once | device_pipeline.py |
LIVE_EVERY_S | 120 | The live analysis is redone each time an ongoing conversation gains this many seconds of speech | device_pipeline.py |
SQUEEZE_KEEP_S | 3 | Before audio goes to the engine, silences longer than this many seconds are cut to this length; 0 sends the audio whole | asr.py |
SQUEEZE_VAD | 0.3 | The VAD threshold that finds those silences | segmenter.py |
SUMMARY_WORKERS | 4 | How many summaries are written at once | summaries.py |
QWEN_ASR_MODEL | qwen-audio-3.1-asr-flash-filetrans | The Bailian model the qwen engine uses | qwen_asr.py |
ASR_CALLS, INFER_CALLS, DB_CALLS, MEDIA_CALLS | 1, 2, 8, 16 | How many local ASR runs, voiceprint computations, whole-database queries and audio encodes or decodes run at once | threads.py |
INFER_THREADS | 4 | onnxruntime threads per voiceprint computation | infer.py |
MUSIC_DETECT | 1 | At finalization, a sentence heard as music (a song played aloud, the voice about as loud as its accompaniment) gets no voiceprint: its speaker is S? and it never reaches the People page. It is still transcribed, with a music-note icon on its row. Fixing its speaker to someone makes it a voice: the icon goes and its voiceprint is learnt as usual. 0 turns it off | music.py |
VAD_GAIN_MAX_DB | 16 | The most dB the VAD's features are raised (this affects only speech detection, not the audio sent to the engine or played back); 0 turns it off | infer.py |
ASR_GAIN_MAX_DB | 16 | The most dB the audio sent to the engine is raised; 0 sends it as is | squeeze.py |
DEHUM | 1 | Removes the recorder's 10 ms periodic hum when its audio is read; 0 turns it off | audio.py |
SPEAKER_LINK, SPEAKER_SUGGEST, MERGE_SUGGEST | 0.5, 0.6, 0.45 | Voiceprint similarity thresholds of the 人物 (People) page: join an anonymous group, suggest a name, suggest merging two labels in the same conversation | routes_speakers.py |
DIAR_TH, DIAR_JOIN, DIAR_GUARD, PEOPLE_MIN | 0.5, 0.35, 0.6, 0.7 | Thresholds for telling speakers apart within a conversation | diarist.py |
SELFVOICE_POP_P | 0.95 | The probability the top candidate needs before the 「哪个是你的声音」 (Which one is your voice) card pops up by itself | selfvoice.py |
LOOP_STALL_MS | 100 | When the event loop is held up longer than this many milliseconds, the log gets one line with how long and in which function (at most a line a second, the ones in between counted), and the perf_stalls table a row; it also measures the loop's lag and the thread pools' use each minute. About 0.6% of a core; 0 turns it off | monitor.py |