Files
serverAppConfigs/scripts/open-notebook.env
T
Frank 032229c1e2 feat(open-notebook): env contract — CPU guard, credential store, auth posture
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.
2026-10-18 16:26:43 -07:00

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