docs(open-notebook): record the deployed shape and what the first render proved

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.
This commit is contained in:
Frank
2026-10-19 16:22:38 -07:00
parent e5580abb6f
commit 69739237c8
4 changed files with 100 additions and 36 deletions
@@ -88,8 +88,29 @@ Verified outcomes of that recipe on 2026-09-02: from inside `hermes-dashboard`
published) and `:9119` (its own published port) connect; `:8080` and `:8787`
are **refused instantly** even though both listeners are bound to `0.0.0.0` in
the host netns. Probes to `host.containers.internal` (`169.254.1.2`) from that
network time out rather than refuse — the alias is an address on the *default*
`podman` network, and this container is not on it.
network time out rather than refuse — but see the 2026-10-19 re-measure below,
where the same alias answers from another `dankclaw_default` container.
### 2026-10-19: which published ports a rootless container can actually dial
Probed from inside `open-notebook` (rootless, on `dankclaw_default` **and** the
private `systemd-open-notebook`), each address aimed at a live listener:
| Dial | Result | Mechanism |
|---|---|---|
| `192.168.0.143:3000` (Gitea) | **200** | published by a **rootless** container — rootless egress crosses the host netfilter where netavark installed that DNAT |
| `192.168.0.143:7770` (`frank-dragoon`) | refused | published by a **root** container. Root and rootless Podman carry separate netavark rule sets, so a rootless container's traffic never crosses root's and a root-published port is invisible to it |
| `192.168.0.143:7771` (`frank-zealot`) | refused | host-netns listener, unpublished — the rule above, reconfirmed |
| `10.89.1.1:7771` | refused | the bridge gateway address belongs to the rootlesskit netns, not to the host |
| `host.containers.internal:7771` (`169.254.1.2`) | **200** | the gateway alias is the one address that carries a rootless container to a host-netns listener. Why `hermes-dashboard` timed out on this alias in 2026-09-02 while `open-notebook` answers today is **not established**; the difference to check first is the second network and where the default route lands |
| `192.168.0.235:7770` (`frank-archon`) | **200** | a different machine: plain routing, no DNAT in the path |
The consequence that changes designs: **a rootless container's menu of local model
endpoints is `frank-tts` (container DNS, same network) and `frank-archon` (LAN
address on another host).** Both Dankclaw endpoints are out of reach —
`frank-dragoon` behind root's publish, `frank-zealot` behind the host netns — and
the only address that does reach them, `169.254.1.2`, is link-local, which some
applications refuse to store at all ([[Open Notebook]] meets exactly that refusal).
## Operational rules
@@ -98,9 +119,13 @@ network time out rather than refuse — the alias is an address on the *default*
host — and never the reverse either: the deployer's health probe runs on the
host, so it sees host-netns listeners that the container itself cannot dial.
- To give a bridge-net container access to a host-netns listener, the options
are (a) move it to `Network=host`, or (b) publish the needed port. Option (b)
makes that port reachable from every netns and from the LAN — an exposure
change that needs operator approval before deployment, not a silent fix.
are (a) move it to `Network=host`, or (b) publish the needed port — but
publishing is advertised **only inside the Podman that owns the container**:
a root container's published port stays invisible to a rootless consumer
(2026-10-19 table above), so the fix must be made on the same root/rootless
side as the consumer. Either way it is an exposure change — a published port
also answers on the LAN — and needs operator approval before deployment, not
a silent fix.
- `chrome-devtools-protocol` is intentionally `Network=host` with a loopback-only
bind so the browser tool stays unreachable from the LAN; do not "tidy" it onto
a bridge network — its two consumers (gateway, webui) are host-netns and reach