The podcast generator stops being a LAN-only box and becomes a fleet service.
Commit
Change
e5580ab
Guarded vhost + post-auth route, single-origin API_URL/CORS_ORIGINS, API publish moved to loopback, LAN A record, homepage card goes live, seam re-pinned
6973923
Open Notebook.md rewritten for the deployed shape; netns census + root-vs-rootless publish rule; frank-tts model-is-ignored fact
Why the shape is what it is
One upstream. The image's own Next.js layer rewrites /api/* to the FastAPI beside it, so Caddy proxies only 192.168.0.143:8502 and the API needs no outward face: PublishPort=0.0.0.0:5056:5055 becomes 127.0.0.1:5056:5055 (the deployer's health gate is the only dialer left).
API_URL must name the vhost.GET /config falls back to autodetect, which appends :5055 — seerr's port on this host. Unset, the UI loads and every call fails. CORS_ORIGINS is the same single origin so the app never defaults to *.
Provider before route was honored: notebook-proxy (Application notebook-guard) was registered and attached to the embedded outpost before any vhost existed. Outpost logged zero wedge lines; seerr/grafana still 200.
The seam derives, it does not restate: API_URL is compared against whatever host the Caddyfile routes to 8502, so renaming the vhost moves the test instead of blessing a stale value.
Proof (measured, not compiled)
./tests/run.sh → 455 passed.
Deploy of the service itself already ran green (images, both units, surreal is-ready exec-form probe, 33/33 Playwright).
First episode rendered end to end: note → outline → 23 dialogue turns → 23 clips → one MP3 on /home/dank/media/podcasts, 747.31 s of speech produced in 301.2 s (2.5× real time, TTS_BATCH_SIZE=1, worker max-tasks 1). Download route: 200 audio/mpeg with the bearer, 401 without.
Two findings worth your eye
The scriptwriter is frank-archon, not frank-zealot. From this container's netns, 192.168.0.143:7771 (host-netns bind) and :7770 (root-published port) both refuse; only host.containers.internal:7771 answers — and open-notebook's URL guard refuses to store any base URL resolving into 169.254.0.0/16, with no override. So the writer is the one fleet LLM that both answers from here and passes that check. Cost: it shares frank-archon's two slots with this operator session. Ways out are in the page (rootless container for zealot, or measure halogen's structured output).
No embedding endpoint exists anywhere in the fleet. Every note save fails embed_note with No embedding model configured. — notes are stored and readable, renders from content work, but semantic search/source ingestion are dead until something serves /v1/embeddings. Not fixable in config: it needs a small llama.cpp slot with --embeddings.
After merge
./scripts/deploy.sh dankclaw --service caddy, --service pihole, --service open-notebook (the env change restarts the app), then a browser walk of the chain: Anubis challenge → Authentik SSO → app login → play the episode. The public Porkbun A record is a host action, not a repo file — LAN resolution ships with this PR.
Built in worktree /home/blank/.omp/wt/wt-onb-portal; files identical to what ./tests/run.sh saw.
## What lands
The podcast generator stops being a LAN-only box and becomes a fleet service.
| Commit | Change |
|---|---|
| `e5580ab` | Guarded vhost + post-auth route, single-origin `API_URL`/`CORS_ORIGINS`, API publish moved to loopback, LAN A record, homepage card goes live, seam re-pinned |
| `6973923` | `Open Notebook.md` rewritten for the deployed shape; netns census + root-vs-rootless publish rule; frank-tts `model`-is-ignored fact |
## Why the shape is what it is
- **One upstream.** The image's own Next.js layer rewrites `/api/*` to the FastAPI beside it, so Caddy proxies only `192.168.0.143:8502` and the API needs no outward face: `PublishPort=0.0.0.0:5056:5055` becomes `127.0.0.1:5056:5055` (the deployer's health gate is the only dialer left).
- **`API_URL` must name the vhost.** `GET /config` falls back to autodetect, which appends `:5055` — seerr's port on this host. Unset, the UI loads and every call fails. `CORS_ORIGINS` is the same single origin so the app never defaults to `*`.
- **Provider before route** was honored: `notebook-proxy` (Application `notebook-guard`) was registered and attached to the embedded outpost before any vhost existed. Outpost logged zero wedge lines; `seerr`/`grafana` still 200.
- **The seam derives, it does not restate**: `API_URL` is compared against whatever host the Caddyfile routes to `8502`, so renaming the vhost moves the test instead of blessing a stale value.
## Proof (measured, not compiled)
- `./tests/run.sh` → **455 passed**.
- Deploy of the service itself already ran green (images, both units, `surreal is-ready` exec-form probe, 33/33 Playwright).
- **First episode rendered end to end:** note → outline → 23 dialogue turns → 23 clips → one MP3 on `/home/dank/media/podcasts`, `747.31 s` of speech produced in `301.2 s` (2.5× real time, `TTS_BATCH_SIZE=1`, worker max-tasks 1). Download route: `200 audio/mpeg` with the bearer, `401` without.
## Two findings worth your eye
1. **The scriptwriter is `frank-archon`, not `frank-zealot`.** From this container's netns, `192.168.0.143:7771` (host-netns bind) and `:7770` (**root**-published port) both refuse; only `host.containers.internal:7771` answers — and open-notebook's URL guard refuses to store any base URL resolving into `169.254.0.0/16`, with no override. So the writer is the one fleet LLM that both answers from here and passes that check. Cost: it shares frank-archon's two slots with this operator session. Ways out are in the page (rootless container for zealot, or measure halogen's structured output).
2. **No embedding endpoint exists anywhere in the fleet.** Every note save fails `embed_note` with `No embedding model configured.` — notes are stored and readable, renders from content work, but semantic search/source ingestion are dead until something serves `/v1/embeddings`. Not fixable in config: it needs a small llama.cpp slot with `--embeddings`.
## After merge
`./scripts/deploy.sh dankclaw --service caddy`, `--service pihole`, `--service open-notebook` (the env change restarts the app), then a browser walk of the chain: Anubis challenge → Authentik SSO → app login → play the episode. The public Porkbun A record is a host action, not a repo file — LAN resolution ships with this PR.
Built in worktree `/home/blank/.omp/wt/wt-onb-portal`; files identical to what `./tests/run.sh` saw.
---
cwd: /home/blank/.omp/wt/wt-20261019-020309-6d1fc1f · session: 01a15366-7601-7175-bb69-c62186c3f8b5
The service came up internal-only: UI on the LAN at 8502, API at 5056, both
published on every interface, with only its own shared password in front. That
put the episode library outside the fleet's auth chain and one port scan away
from anything on the network.
- Caddy: guarded vhost notebook.franksdank.cloud (Anubis -> Authentik; provider
notebook-proxy, internal_host caddy:8017) plus the post-auth route to the
published UI port. One upstream is enough, because the image's own Next.js
layer rewrites /api/* to the FastAPI beside it — so the API needs no outward
face at all, and 5056 moves to loopback, where only the deployer's health gate
dials it.
- scripts/open-notebook.env: API_URL and CORS_ORIGINS become the single guarded
origin. Left to itself the app auto-detects <host>:5055, the port seerr holds
on this host; a LAN-origin API_URL would also take the browser around the
guard chain, where its authenticated session does not ride.
- addn-hosts: the LAN A record. The homepage card goes live and links the vhost.
- Seam: the API origin is derived from whatever the Caddyfile routes to 8502, so
renaming the vhost moves the test instead of leaving it blessing a stale name,
and a new test proves the vhost hands the WHOLE request to Anubis.
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.
dank
merged commit 8bac97ec87 into main2026-10-19 16:24:11 -07:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What lands
The podcast generator stops being a LAN-only box and becomes a fleet service.
e5580abAPI_URL/CORS_ORIGINS, API publish moved to loopback, LAN A record, homepage card goes live, seam re-pinned6973923Open Notebook.mdrewritten for the deployed shape; netns census + root-vs-rootless publish rule; frank-ttsmodel-is-ignored factWhy the shape is what it is
/api/*to the FastAPI beside it, so Caddy proxies only192.168.0.143:8502and the API needs no outward face:PublishPort=0.0.0.0:5056:5055becomes127.0.0.1:5056:5055(the deployer's health gate is the only dialer left).API_URLmust name the vhost.GET /configfalls back to autodetect, which appends:5055— seerr's port on this host. Unset, the UI loads and every call fails.CORS_ORIGINSis the same single origin so the app never defaults to*.notebook-proxy(Applicationnotebook-guard) was registered and attached to the embedded outpost before any vhost existed. Outpost logged zero wedge lines;seerr/grafanastill 200.API_URLis compared against whatever host the Caddyfile routes to8502, so renaming the vhost moves the test instead of blessing a stale value.Proof (measured, not compiled)
./tests/run.sh→ 455 passed.surreal is-readyexec-form probe, 33/33 Playwright)./home/dank/media/podcasts,747.31 sof speech produced in301.2 s(2.5× real time,TTS_BATCH_SIZE=1, worker max-tasks 1). Download route:200 audio/mpegwith the bearer,401without.Two findings worth your eye
frank-archon, notfrank-zealot. From this container's netns,192.168.0.143:7771(host-netns bind) and:7770(root-published port) both refuse; onlyhost.containers.internal:7771answers — and open-notebook's URL guard refuses to store any base URL resolving into169.254.0.0/16, with no override. So the writer is the one fleet LLM that both answers from here and passes that check. Cost: it shares frank-archon's two slots with this operator session. Ways out are in the page (rootless container for zealot, or measure halogen's structured output).embed_notewithNo embedding model configured.— notes are stored and readable, renders from content work, but semantic search/source ingestion are dead until something serves/v1/embeddings. Not fixable in config: it needs a small llama.cpp slot with--embeddings.After merge
./scripts/deploy.sh dankclaw --service caddy,--service pihole,--service open-notebook(the env change restarts the app), then a browser walk of the chain: Anubis challenge → Authentik SSO → app login → play the episode. The public Porkbun A record is a host action, not a repo file — LAN resolution ships with this PR.Built in worktree
/home/blank/.omp/wt/wt-onb-portal; files identical to what./tests/run.shsaw.cwd: /home/blank/.omp/wt/wt-20261019-020309-6d1fc1f · session: 01a15366-7601-7175-bb69-c62186c3f8b5