nornir viz — mode resolution (fat · thin · server)
How nornir viz (and the three deployment modes) decide where the data comes from when you launch the visualizer. Decided with Rickard, 2026-06-12.
TL;DR
- Standing in / pointing at a
workspace_<name>/→ FAT (your local~/.nornir). - No workspace config found → THIN CLIENT, out of the box → a plain remote call to a local server on
:7878(shows all the server's workspaces). - It "just works": no env-var to set, no flags.
cd /tmp && nornir vizshows the server's workspaces;cd workspace_korp && nornir vizshows your local one.
The resolution rule (what run_viz does)
In order:
NORNIR_SERVERset → THIN (explicit override; unchanged).--warehouse <dir>given → FAT/embedded against that dir (explicit).-
Otherwise, is a workspace config found? —
--config <path>, or config discovery walking up fromcwdinto aworkspace_<name>/release/nornir.toml:- found → FAT/embedded (nornir-ui reads your
~/.nornir). - not found → THIN CLIENT: probe
127.0.0.1:7878; if a server is up, connect to it (a remote call) with the token from the user-readable~/.nornir/token. If no server is reachable → an empty local view plus a hint (cdinto a workspace, or start a server).
- found → FAT/embedded (nornir-ui reads your
So "no config ⇒ thin" is the whole idea: if you haven't told nornir which workspace you mean, the most useful thing is to show the server's catalog.
The token
Thin mode needs a bearer token. Resolution: $NORNIR_SERVER_TOKEN, else the user-readable ~/.nornir/token (mode 600). nornir writes that file so a human who administers the box gets out-of-the-box thin access without sudo-reading /etc/nornir/nornir.env.
The three modes are EQUAL — add populates, the poll loop only updates
The fat user does exactly what the server does; the server just automates the "stay fresh" step.
| what it does | fat (you) | server (nornir user) |
|
workspace register <ws_X> (the add) |
record the workspace + its members from the descriptor | ✅ same command | ✅ same command |
populate = fetch (clone members) + build → ~/.nornir/workspaces/<name>/builds/warehouse |
fill the warehouse so it's viewable | ✅ you run it (fetch + a build) |
✅ done on register/poll |
| poll loop | automatically re-fetch + rebuild on a git HEAD change — the update path only | ✗ (no daemon; you re-run on demand) | ✅ the only server-extra |
Design intent (target): make add eager — register → fetch → build inline so the warehouse is populated now (immediate feedback), and demote the poll loop to update-only (it watches git and re-runs the build on change). Then fat and server differ only in who pushes the update button — the poll loop is just the automated add/update.
Data lives under the home-derived root either way (see data-root.md): ~/.nornir/workspaces/<name>/ for you, /home/nornir/.nornir/workspaces/<name>/ for the server — same code, same shape.
Forcing a mode
- Force thin to a specific server:
nornir viz --server http://oden:7878(a barehost:portgetshttp://prepended).--serveroverrides auto-detection. - Force fat (even with a server up):
nornir viz --warehouse ~/.nornir/workspaces/<name>/builds/warehouse, ornornir --config workspace_<name>/release/nornir.toml viz.
Connect a viz client to a server (the full flow)
The end-to-end "I want to watch a server's workspaces" path:
# 1. On the SERVER host — install + run it (see running-as-a-server.md):
cargo install nornir --features viz # → nornir, nornir-server, nornir-ui
sudo nornir install add --token <≥16-char-secret> --addr 0.0.0.0:7878
# (`install` is idempotent + PRESERVES the token/ssh key across upgrades)
# 2. From ANY machine — point the viz at it:
nornir viz --server http://<host>:7878 --token <the-secret>
That's it — --server makes it a thin client (a plain gRPC call), so the picker shows all the server's workspaces. Switching the top picker re-scopes every tab live.
Every data-bearing tab fills from the server, not just Timeline/Warehouse: 🚀 Release (Viz.ReleaseEvents), 🏆 Leaderboard (Viz.BakeoffResults), 🗺 Knowledge (Viz.Knowledge — the server scans its cloned checkouts), 📡 Live Run (durable hydrate via Viz.ReleaseEvents + the Release.Progress live stream), 🗂 Funnel (Funnel.Show). The 🔗 Dep Graph derives from the Timeline, so it builds wherever Timeline does. The full tab → RPC → table map and the "why is a tab empty in thin mode" guide live in viz-remote-rpcs.md.
The token — how it's resolved
The bearer token (≥16 chars) is the app-level guard; every RPC carries it. nornir viz looks for it in this order:
--token <secret>on the command line, else$NORNIR_SERVER_TOKENin the environment, else~/.nornir/token— a user-readable file (mode 600). Drop the secret there once and barenornir viz(auto-thin) +nornir viz --server …both pick it up with no flags. (On the box that runs the server, the token is the secret you passed tonornir install; it also lives in/etc/nornir/nornir.env, readable only by root — so~/.nornir/tokenis the convenient user-space copy.)
So: server admin chooses the secret at nornir install; clients get that secret (shared over a trusted channel / tailnet) and either pass --token, export NORNIR_SERVER_TOKEN, or save it to ~/.nornir/token.
See also: viz-remote-rpcs.md (the thin read path: tab → RPC → table, + adding a new remote-backed tab), running-as-a-server.md (install + run the server), howto-remote-collaborator.md (Tailscale remote access), mode-matrix.md (the full verb×mode contract), data-root.md (the ~/.nornir layout).