# open-notebook.env — configuration for **open-notebook**, the fleet's podcast # generator on Dankclaw (user path https://notebook.franksdank.cloud; UI host # port **8502**, API host port **5056 loopback-only**). 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. 660 sits one # round-trip OUTSIDE SHIM_TIMEOUT=600 in scripts/frank-tts.env — the shim's own # per-request ceiling — so a frank-tts give-up arrives as frank-tts's error and # not as open-notebook abandoning a synthesis still in flight. 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 the endpoint # provisioned today answers them well inside 180. The provider itself is chosen # in the database, not here — [[Open Notebook]] owns that provisioning and the # address census behind it. Short version: frank-zealot was the plan (script # writing asks for a JSON-schema response, which llama.cpp serves and halogen's # behaviour on frank-dragoon is UNVERIFIED), but no Dankclaw LLM is dialable from # this container's netns, and the single address that reaches one — # `host.containers.internal`, 169.254.1.2 — the app refuses to store at all, # because link-local is a blocked address class. The writer is therefore # frank-archon (`http://192.168.0.235:7770/v1`), the only fleet LLM that both # answers from inside this netns and passes that check. # # 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 guarded vhost # (notebook.franksdank.cloud → Caddy → 8502) is the only user path, and the # image's own Next.js layer rewrites `/api/*` to the FastAPI running beside it, # so ONE origin serves UI and API and API_URL has to name that vhost. This is # not cosmetic: unset, the app auto-detects `:5055` — the port # upstream ships, and the one seerr holds on this host — and hands the browser # an address nothing answers; pointed at the LAN `:5056` instead, every call # leaves the guarded origin and an authenticated browser session does not ride # it. Same-origin traffic needs no CORS at all, but CORS_ORIGINS stays set to # the one origin because the app warns at start while it is unset and defaults # to `*` — a wildcard in front of an endpoint that serves every episode's # audio. No trailing slash: the UI concatenates `${API_URL}/api/...`. API_URL=https://notebook.franksdank.cloud CORS_ORIGINS=https://notebook.franksdank.cloud # 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=660 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