docs(open-notebook): runbook, episode-destination interim, and the log entry
Page registered in the vault index (the docs-completeness seam fails an unregistered page) and logged in the bundle change log. What the page owes the reader: the provider contract (frank-zealot for scripts with a /v1 base URL, frank-tts for voices with 1-4 speakers mapped to the narrator1/2/3 voice packs typed as free text), why Test Connection and Discover Models fail cosmetically against frank-tts rather than being a fault, the encryption key as the precondition for saving any credential, and the honest blocker — frank-tts is not deployed and nothing listens on 7772, so a generation run writes its script and fails on the first voice. Episode destination is stated as interim and cites Gitea issue #7 (human-only, agents must not work it): the app keeps its named volume, one bind mount puts finished MP3s under /home/dank/media/podcasts, and deleting that one line is the whole rollback. Rollback section also covers the full undo, including that the podman network only disappears once the units are gone.
This commit is contained in:
@@ -2311,3 +2311,82 @@ every restart since the cutover.
|
||||
Kokoro-enum-only — so "the agent talks in my voice" needs that provider row or
|
||||
a `pi.registerProvider` extension, decided in phase 2.
|
||||
|
||||
## 2026-10-18 — open-notebook: the podcast generator's config lands (nothing deployed)
|
||||
|
||||
- Goal: **open-notebook** as the fleet's podcast generator on Dankclaw — notes
|
||||
and sources in, a multi-speaker episode out, script written by
|
||||
**frank-zealot**, voices rendered by **frank-tts**, no cloud provider
|
||||
anywhere in the path. Repo-side only: no host touched, no container pulled or
|
||||
started. Runbook: [[Open Notebook]].
|
||||
- **Two containers, not one.** `lfnovo/open_notebook:v1-latest` plus
|
||||
`surrealdb/surrealdb:v2`, both **digest-pinned** with `Pull=never` and a
|
||||
documented one-time `podman pull` — frank-dragoon's pinning style, because
|
||||
`:latest` moving underneath a rootless service is how you get an unbootable
|
||||
pod after an unrelated `systemctl-daemon-reload`. The registry round-trip is
|
||||
what proved `v2` is SurrealDB **2.7.0** and that the app image is Debian
|
||||
trixie with **curl and ffmpeg already inside** — which is how both health
|
||||
probes can be argument-only: `curl -fsS …/health` (route is upstream's,
|
||||
`api/main.py:239`, and it sits on the auth exclude list) and
|
||||
`/surreal is-ready`, which I ran against the digest here: green in 2 s, and
|
||||
it refuses to pass without `SURREAL_PASS` in the environment — a credential
|
||||
that must never ride the command line, so `Exec=` carries flags only.
|
||||
- **Host 5055 was already spoken for.** seerr has held `127.0.0.1:5055` since
|
||||
2026-10-14 (live `ss -ltn` agrees today), so the API publishes
|
||||
`0.0.0.0:5056:5055`. open-notebook's frontend derives its API URL from the
|
||||
window origin when `API_URL` is unset — the upstream env reference spells the
|
||||
reverse-proxy symptom — so a silent remap would hang the UI on a port nobody
|
||||
listens on, and `API_URL=http://192.168.0.143:5056` is set accordingly. 8502
|
||||
publishes on `0.0.0.0` because this service carries its own password (the
|
||||
episode-audio route is unauthenticated), unlike the loopback-and-proxied
|
||||
observability stack; **8000 is published nowhere**, where upstream exposes it
|
||||
on loopback with root credentials, and the seam forbids it.
|
||||
- **The CPU guard, pre-armed.** `frank-tts` spawns one ~8 GiB, 8-thread engine
|
||||
per request on a box that also serves two LLMs, while open-notebook defaults
|
||||
to `TTS_BATCH_SIZE=5` × `OPEN_NOTEBOOK_WORKER_MAX_TASKS=5` — a five-way
|
||||
self-DoS. Both are pinned to `1` in `scripts/open-notebook.env`, and the
|
||||
worker knob is only real because the image's own supervisord expands
|
||||
`${OPEN_NOTEBOOK_WORKER_MAX_TASKS:-5}` (`supervisord.conf:25`). Both
|
||||
`ESPERANTO_*` timeouts go above upstream's 300 s for CPU synthesis.
|
||||
- **What a credential needs in order to exist:** `OPEN_NOTEBOOK_ENCRYPTION_KEY`
|
||||
must be set before the UI can save *any* provider credential, and
|
||||
`OPEN_NOTEBOOK_PASSWORD` must stay **absent** to remain auth-off — the app
|
||||
reads `os.environ.get(…) is not None`, so `PASSWORD=` boots a login form
|
||||
asking for the empty string. The repo env file carries both keys empty; the
|
||||
deployer seeds `/home/dank/Containers/open-notebook/env` (600) from the host
|
||||
vault and **refuses to start the pair while either secret is blank**, because
|
||||
a half-configured open-notebook stores no credentials and fails quietly.
|
||||
- **Episode destination is interim, by decision.** The destination is **Gitea
|
||||
issue #7, `human-only`** — agents must not work it. Until Frank settles it,
|
||||
the app keeps its own named volume and `/home/dank/media/podcasts` is
|
||||
bind-mounted over `/app/data/podcasts` (`DATA_FOLDER` is hard-coded `./data`),
|
||||
so finished MP3s are ordinary files in his home directory instead of volume
|
||||
contents needing `podman unshare` — the database volume demonstrably lands on
|
||||
a mapped subuid, measured with the pinned image. One mount line; one deletion
|
||||
undoes it.
|
||||
- **Two UI buttons will fail, and that is cosmetic.** Test Connection does
|
||||
`GET <base>/models` and the TTS probe synthesises voice `alloy`; frank-tts
|
||||
serves `/health` and `/v1/audio/speech` only. Model rows are created *by
|
||||
name* and the voice field is free text, so `narrator1/2/3` is typed in and
|
||||
never discovered. Documented, not worked around.
|
||||
- **Blocked on frank-tts, stated plainly:** phase 1 landed without deployer or
|
||||
homepage registration and nothing listens on 7772 today, so a generation run
|
||||
writes its script and then fails on the first voice. That is upstream's
|
||||
creation-podcasts warning in fleet clothing.
|
||||
- Suite green: **403/403** (`./tests/run.sh`, wall 67 s) — 19 tests in the new
|
||||
`tests/open_notebook.bats`, structure only: pins, boot persistence, the
|
||||
unpublishable database port, the 5055→5056 remap plus the `API_URL` that has
|
||||
to match it, the env-file read order, the guard knobs, the health gate naming
|
||||
`/health`, planner routing, the homepage card hidden while undeployed.
|
||||
- Three deployer seams cost time and are now law-shaped: a service must be
|
||||
registered in **five** places — `SERVICE_ORDER`, `DANKCLAW_SERVICES`, the
|
||||
`deploy-remote.sh` dispatch case, its pre-deploy requirement, and
|
||||
`deploy.sh`'s dispatch regex, where an omission silently plans an empty set
|
||||
for `--only`; a `dankclaw/<svc>/*` case must precede the `dankclaw/*.container`
|
||||
glob because that `*` crosses `/`; and the changed-path routing lives *inside*
|
||||
the per-path loop, after the `scripts/*.env)` case, not at the top of the file.
|
||||
- Deliberately NOT done: the deploy (pulling 1.27 GiB to Dankclaw was not
|
||||
asked for), any Caddy vhost or public exposure, audiobook/EPUB (issue #5), any
|
||||
`frank-tts` code change, and the harbor templates on Dankserver —
|
||||
`~/.harbor/compose.opennotebook.yml` pins the deprecated `-single` image,
|
||||
carries no encryption key and injects the whole harbor env — left untouched,
|
||||
read-only, as the record of what not to copy.
|
||||
|
||||
Reference in New Issue
Block a user