The page described an undeployed service whose scriptwriter was going to be frank-zealot. Both halves are now false, and the reason is worth more than the correction. - Status, ports, coordinates: deployed 2026-10-19, guarded vhost, API published on loopback only. The first episode is the proof: 747 s of speech in 301 s of wall clock (2.5x real time, knobs at 1), download route 200 with the bearer and 401 without. - New section for the guard chain: provider-before-route, one upstream because the image rewrites /api/* itself, and the API_URL law — autodetect appends :5055, which is seerr's port on this host, so an unset API_URL yields a UI that loads and an app that cannot call itself. - Provisioned records table (credentials, model rows, dank-duo, dank-daily) and the two contract traps measured on the live API: generate takes profile NAMES, and the fleet has no /v1/embeddings, so every note save fails its embed_note command while renders from content work fine. - Why frank-archon writes the scripts: frank-zealot is reachable from this netns only at 169.254.1.2, and open-notebook's URL guard refuses to store a link-local base URL with no override. Census lands on [[Container Network Namespaces]], which also gains the root-vs-rootless publish rule — a root-published port is invisible to a rootless container, so "publish the port" was the wrong remedy as written there. - Frank TTS: the request's model field is ignored; only voice selects a pack. Tests: ./tests/run.sh, 455 passed.
111 lines
6.6 KiB
Bash
111 lines
6.6 KiB
Bash
# 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 `<host-you-opened>: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
|