The two knobs that keep the box alive: TTS_BATCH_SIZE=1 and
OPEN_NOTEBOOK_WORKER_MAX_TASKS=1. Upstream ships 5 x 5; frank-tts spawns one
~8 GiB, 8-thread engine process per synthesis request on a machine that also
serves two LLMs, so a five-way fan-out is a self-inflicted shutdown. The worker
knob is real, not decorative: the image's supervisord expands
${OPEN_NOTEBOOK_WORKER_MAX_TASKS:-5} into the worker command
(supervisord.conf:25).
Both ESPERANTO_* timeouts go above upstream's 300 s defaults, sized for CPU
synthesis (SHIM_TIMEOUT=600, LLAMA_TTS_THREADS=8) — one episode is dozens of
per-line /audio/speech calls and a timeout kills the episode, not the line.
API_URL is forced for the 5055 -> 5056 remap; SURREAL_URL addresses the database
by container name; CORS_ORIGINS is origin-form (scheme://host:port), the form
the app compares against. OPEN_NOTEBOOK_PASSWORD stays absent here on purpose:
the app treats a set-but-empty value as auth-on.
Secrets stay placeholders in the tracked file and in dankclaw/.env.example, which
lists SURREAL_PASSWORD rather than SURREAL_PASS — the deployer derives the
surreal CLI's spelling from it when it seeds, so the app and the database cannot
be handed different values.
100 lines
5.8 KiB
Bash
100 lines
5.8 KiB
Bash
# open-notebook.env — configuration for **open-notebook**, the fleet's podcast
|
|
# generator on Dankclaw (UI host port **8502**, API host port **5056**). Read by
|
|
# BOTH Quadlet units (dankclaw/open-notebook/quadlet/) as the FIRST
|
|
# EnvironmentFile= line, which is why every path and host name below is the
|
|
# value the CONTAINER must see.
|
|
#
|
|
# TWO FILES, ONE ORDER. This file is tracked; it holds knobs, not credentials.
|
|
# The three secret keys below are declared EMPTY on purpose — the `scripts/*.env`
|
|
# gitignore exception exists for "paths and numbers, never credentials" — and the
|
|
# real values live in the host file the deployer seeds at
|
|
# /home/dank/Containers/open-notebook/env (mode 600, seeded from dankclaw/.env,
|
|
# the same convention grafana/marreta/pihole follow). Both files are handed to
|
|
# podman in EnvironmentFile= order and the LAST one wins (verified against the
|
|
# generated run line), so the host file overrides these blanks. Do not reorder
|
|
# the two lines.
|
|
#
|
|
# WHY THE DEPLOYER REFUSES TO START THESE UNITS WITHOUT THE SECRET FILE. An empty
|
|
# OPEN_NOTEBOOK_PASSWORD is not "unconfigured", it is **authentication off**, and
|
|
# the episode-audio download is one of the routes the API serves without a
|
|
# session; an empty SURREAL_PASS lets the database come up with different root
|
|
# credentials than the app will present; an empty OPEN_NOTEBOOK_ENCRYPTION_KEY
|
|
# means no provider credential can be saved at all. So the guard is in
|
|
# deploy-remote.sh (seed_open_notebook_env), not in the app's opinion.
|
|
#
|
|
# THE CPU-SHUTDOWN GUARD (the two knobs that matter most on this box). Defaults
|
|
# are TTS_BATCH_SIZE=5 and OPEN_NOTEBOOK_WORKER_MAX_TASKS=5 — five parallel
|
|
# synthesis requests from five parallel jobs. frank-tts answers each request by
|
|
# spawning its own engine process (~8 GiB resident, 8 threads, measured in the
|
|
# frank-tts CPU trial of 2026-10-18), so the defaults mean ~40 GiB and forty
|
|
# threads aimed at a machine that also serves frank-dragoon and frank-zealot and
|
|
# whose TTS engine runs on the SAME CPU. Both numbers are 1 here: one episode at
|
|
# a time, one voice line synthesized at a time. That is a throughput choice, not
|
|
# a correctness one — a podcast is a background job — and it is the difference
|
|
# between a slow render and a box that stops answering the text endpoints
|
|
# mid-sentence. Raise them only with a TTS engine that batches internally.
|
|
#
|
|
# THE TTS CLOCK. ESPERANTO_TTS_TIMEOUT is the worker's patience per DIALOGUE
|
|
# LINE, not per episode: one episode is dozens of POSTs to /v1/audio/speech
|
|
# (the app synthesises each speaker turn separately and concatenates the clips
|
|
# with ffmpeg afterwards), and a CPU engine takes single-digit seconds for a
|
|
# line, with the tail of a long turn well past the 300 s default. 600 matches
|
|
# SHIM_TIMEOUT in scripts/frank-tts.env — the shim's own per-request ceiling —
|
|
# so open-notebook never abandons a synthesis frank-tts is still finishing. An
|
|
# abandoned request does not cancel the engine process; it just leaves the
|
|
# episode stuck in "generating" while the audio arrives.
|
|
#
|
|
# THE SCRIPTWRITER. ESPERANTO_LLM_TIMEOUT is the app's own default (180 s), kept
|
|
# visible beside the TTS number because both belong to one podcast run: the
|
|
# outline call and the transcript call are the slow text work, and frank-zealot
|
|
# answers them well inside 180. Provider choice is made in the UI, not here —
|
|
# see [[Open Notebook]] for the one-time credential provisioning — and it is
|
|
# frank-zealot on purpose: script writing asks for a JSON-schema response, which
|
|
# the OpenAI-compatible llama.cpp endpoint is known to serve, while halogen's
|
|
# structured-output behaviour on frank-dragoon is UNVERIFIED. halogen remains
|
|
# the unproven alternative, not the fallback.
|
|
#
|
|
# THE DATABASE COORDINATES. SURREAL_URL addresses the sibling container BY NAME
|
|
# on systemd-open-notebook (container DNS is scoped to the owning network, so
|
|
# this is the only form that works). NAMESPACE/DATABASE/USER are plain names.
|
|
# SURREAL_PASSWORD and SURREAL_PASS are ONE value under two spellings: the app
|
|
# reads SURREAL_PASSWORD, the `surreal` CLI reads SURREAL_PASS. Keep them equal;
|
|
# the deployer writes both into the seeded file from a single SURREAL_PASSWORD in
|
|
# dankclaw/.env, so hand-editing one line is the only way to desync them.
|
|
|
|
SURREAL_URL=ws://open-notebook-surrealdb:8000/rpc
|
|
SURREAL_NAMESPACE=open_notebook
|
|
SURREAL_DATABASE=open_notebook
|
|
SURREAL_USER=open_notebook
|
|
# Secrets — filled on the host, never here (see the header).
|
|
SURREAL_PASSWORD=
|
|
SURREAL_PASS=
|
|
OPEN_NOTEBOOK_ENCRYPTION_KEY=
|
|
OPEN_NOTEBOOK_PASSWORD=
|
|
|
|
# WHERE THE BROWSER FINDS THE API. The UI auto-detects `<host-you-opened>:5055`,
|
|
# and 5055 on this host belongs to seerr, so the API leaves the container on 5056
|
|
# and this line has to say so. Both origins are listed in CORS_ORIGINS because
|
|
# the API is called cross-origin from the UI's own port: the LAN origin is the
|
|
# normal one, the localhost one covers a browser opened on Dankclaw itself. The
|
|
# app logs a warning at start while CORS_ORIGINS is unset and defaults to `*`,
|
|
# which is a wildcard in front of an endpoint that serves every episode's audio.
|
|
API_URL=http://192.168.0.143:5056
|
|
CORS_ORIGINS=http://192.168.0.143:8502,http://localhost:8502
|
|
|
|
# Concurrency: see "THE CPU-SHUTDOWN GUARD" above. Defaults are 5 and 5.
|
|
TTS_BATCH_SIZE=1
|
|
OPEN_NOTEBOOK_WORKER_MAX_TASKS=1
|
|
|
|
# Timeouts: see "THE TTS CLOCK" and "THE SCRIPTWRITER" above.
|
|
ESPERANTO_TTS_TIMEOUT=600
|
|
ESPERANTO_LLM_TIMEOUT=180
|
|
|
|
# Heavy extraction runtimes stay OFF, which is also what keeps boot fast: each
|
|
# one installs a multi-gigabyte ML/Chromium stack into the image's venv on first
|
|
# startup (cached on the /app/data volume after that). Podcast generation reads
|
|
# notes and sources that are already text, so neither is needed here; enable one
|
|
# deliberately, per the upstream docs, if source ingestion starts to matter.
|
|
# OPEN_NOTEBOOK_ENABLE_DOCLING=true
|
|
# OPEN_NOTEBOOK_ENABLE_CRAWL4AI=true
|