Files
mesh-catalog/modules/nats/README.md
T
jochen e6d5ead50d
mesh/merge-gate head e6d5ead50dfd merged into trunk 44e7002d3c84 — fail: builds nats, sent nowhere; merging this needs a person's bus upgrade; 1 wait(s) fo…
mesh/repo-check head e6d5ead50dfd merged into trunk 44e7002d3c84 — fail: head e6d5ead50dfd does not merge cleanly into trunk 44e7002d3c84: in conflict modu…
mesh/delivery stopped: the pull request closed unmerged
nats: a WebSocket listener for a page in a browser, from the overlay only (hq research 036)
The bus has no way for a browser to reach it (research 036, gap G1). The server now also listens
over WebSocket on 4223: published from the mesh like 4222, so the machine's filter admits it from
the overlay and nothing else; no TLS, acceptable only because the overlay is WireGuard and a
browser cannot be made to trust the mesh's own certificate; compression on. The user a page
connects as is the read-only view the controller composes (mesh-controller, `bus view-credential`).

A test parses the declared configuration with the server itself, holds the opening, the published
port and the listener to one port, and connects a client over WebSocket. The bus module's policy is
`record`: this reaches the bus's machine only through the planned step a person starts
(`bus upgrade`), as every bus change does.
2026-10-10 16:00:52 +02:00

99 lines
6.0 KiB
Markdown

# nats — the mesh's bus
The bus server (novox/hq design 25), the configuration the module owns (`nats.conf`: ports, TLS,
JetStream's store) and the user list the controller composes (`accounts.conf`), reloaded in place by
the entrypoint. Its tools (`cmd/nats-tools`) are served by the node's runtime and only read.
## Its ports
`4222` is the bus: TLS, the mesh's own certificate, every link the mesh has. `4223` is the same server over
WebSocket, for a client in a browser (nats.ws): the issue tracker's view page, connecting directly as the
read-only user the controller composes for it (`bus view-credential`; novox/hq research 036). Both are
published from the mesh only — the machine's filter admits them from the overlay and nothing else — and the
WebSocket listener has no TLS *because* of that: the overlay is WireGuard, so every byte reaching it crossed
the tunnel, and a browser cannot be made to trust the mesh's own certificate. The user still authenticates
with the password the mesh minted, and is granted reads and nothing else. `8222` is the server's monitoring,
on the machine's loopback alone.
## Its data, and how it is backed up
Everything the bus keeps is in JetStream: every stream, and every key-value bucket (a stream named
`KV_<bucket>`) — conditions and their history, calls, hand-acts, the controller's lease, assignments,
events, every module's state.
**The store's files are never what is backed up.** They are written all the time, and a copy taken
while the server writes them may not restore. The `jetstream` data item is protected by a dump
instead (novox/hq ADR 0235, to-be 43): each night the machine's backup holder runs
docker exec -i mesh-broker-nats mesh-nats-snapshot snapshot < <the module's broker credential> > <snapshots>/bus.tar
and backs up the `snapshots` directory. `mesh-nats-snapshot` (`snapshot/`, built into this image)
asks the server for each stream through JetStream's snapshot API, one at a time, acknowledging each
chunk so the server's flow control keeps moving; the server goes on taking writes and no stream is
paused or reconfigured. It writes one tar on stdout:
- `manifest.json` — when it was taken and how long it took, the server's version, and per stream its
subjects, messages, bytes, first and last sequence, consumers, and the size and SHA-256 of its files;
a memory stream is listed as skipped, with why;
- `streams/<name>/backup.json` — the stream's configuration and state, as the server gave them;
- `streams/<name>/stream.tar.s2` — the server's archive of the stream, consumers included.
It runs as the module's own bus account, which the controller grants the snapshot API and nothing
else — no stream may be defined, changed, purged or written by it (mesh-controller
`internal/broker`, `BusSnapshotGrants`). A failure fails that night for the module, leaves the
previous `bus.tar` in place, and reaches the operator as `backup-stale` once the last good night is
past its bound.
**What a snapshot promises.** Every message up to the last sequence its manifest gives for a stream is
in it, exactly. A stream written to while it is read out may hold some messages past that sequence
too — the server reads its blocks a moment after it states the stream — and a restore of it ends at
or after the manifest's sequence, never before.
## Restoring
Nothing is restored over the live bus: a stream is only ever restored where it does not exist, and
the controller asserts every stream on start. So the bus is restored into a **new store beside the
live one**, which a person swaps in.
1. **Take the night back, beside the live data.** `node-backup.restore` for `nats` on the bus's
machine (optionally naming a restore point) restores the snapshots directory as
`<snapshots>.restored-<stamp>/`, holding that night's `bus.tar`.
2. **Check it.** `docker exec -i mesh-broker-nats mesh-nats-snapshot verify < <restored>/bus.tar` —
every file against the manifest's checksum, every archive read to its end.
3. **Build the new store**, with the bus's own image (the server that fills it is the same release
the bus runs):
image=$(docker inspect -f '{{.Config.Image}}' mesh-broker-nats)
docker run --rm -v <restored>:/in:ro -v <new-store>:/out \
--entrypoint mesh-nats-snapshot "$image" restore --into /out --from /in/bus.tar
It starts a server of its own on the container's loopback with the bus's one account (`MESH`),
restores every stream, holds each to the manifest, stops it, and leaves
`<new-store>/jetstream/MESH/streams/…` — the bus's layout.
4. **Swap it in — a person's act, a planned bus step** (novox/hq to-be 45): say it as a hand act,
then in one command line, because the node-engine starts a stopped container again at its next
reconcile (within five minutes):
docker stop mesh-broker-nats && \
mv <jetstream-data>/jetstream <jetstream-data>/jetstream.before-restore-<stamp> && \
mv <new-store>/jetstream <jetstream-data>/jetstream && \
docker start mesh-broker-nats
The store kept aside is removed by a person once the bus is seen whole (`nats_streams`, the
controller's `doctor`).
A single stream can be restored into any server that does not hold it with
`mesh-nats-snapshot restore --server <url> --credential <file>`; the archive is also what the nats CLI
reads (`nats stream restore <dir>` on an unpacked `streams/<name>/`).
## Tests
- `snapshot/`: `MESH_TEST_DOCKER=1 go test -race ./...` — a throwaway `nats:2.11` filled like the
mesh's bus (events with a gap deleted, a work queue part worked, buckets with history, deletes and a
purge, an object, durable consumers, a memory stream) and written to throughout the snapshot; the
snapshot restored into a fresh server and into a new store served by a third, each compared message
by message; a damaged archive refused; and the snapshot user refused every write.
`MESH_TEST_NATS_IMAGE=<this image>` also runs the manifest's declared dump, exactly, against the
module's image and configuration.
- `cmd/nats-tools`: `go test ./...`.