Data root — <home>/.nornir
TL;DR (the whole thing in four lines)
- nornir keeps everything in
~/.nornir. That's it. - One folder per workspace under
~/.nornir/workspaces/<name>/. - The server is identical, just under
/home/nornir/.nornir(because the daemon runs as thenorniruser). - Run
nornir rootto print where it is. No env-var to set; containers just set$HOME.
The layout
nornir's data root is derived from the running user's home directory. ONE rule for everyone, no path env-vars. Everything is per-workspace — there is no global warehouse or funnel; a workspace is one self-contained folder:
<home>/.nornir/
workspaces/
registry.redb # the ONLY global file (the index: which workspaces exist)
<name>/ # e.g. holger, korp, matrix — one folder per workspace
git/ # member source: server clones here / fat = your external checkout
builds/
warehouse/ # THIS workspace's Iceberg warehouse
funnel/ # THIS workspace's idea→plan funnel
The payoff: fat and server modes are the identical layout — the only difference is who fills git/ (you check it out, or the server clones it). A workspace_<name> bundles as many repos as you want, so breadth lives inside a workspace, not in a global pile.
<home> is the running user's home directory:
| who runs it | $HOME |
data root |
a login user rickard |
/home/rickard |
/home/rickard/.nornir |
the server (system user nornir) |
/home/nornir |
/home/nornir/.nornir |
The same code computes both — there is no special case for the server.
How home is resolved
config::nornir_home() calls the home crate's home_dir(), which reads $HOME and falls back to getpwuid for the current uid. That makes it correct for:
- a normal interactive login (
$HOMEset); - the systemd service (
Environment=HOME=/home/nornirin the unit); sudo -H -u nornir …(the-Hresets$HOMEto nornir's, and getpwuid backs it up).
The canonical resolvers live in src/config.rs:
pub fn nornir_home_from(home: &Path) -> PathBuf // pure: home.join(".nornir")
pub fn nornir_home() -> PathBuf // nornir_home_from(home::home_dir())
pub fn registry_root() -> PathBuf // nornir_home().join("workspaces")
// per-workspace, on `Loaded` (keyed by the workspace_<name> in the config path):
pub fn Loaded::workspace_name(&self) -> String // "matrix" from …/workspace_matrix/…
pub fn Loaded::warehouse_root(&self) -> PathBuf // registry_root()/<name>/builds/warehouse
pub fn Loaded::funnel_root(&self) -> PathBuf // registry_root()/<name>/funnel
Every root/warehouse/funnel default routes through these. An explicit [storage].local_path still overrides the per-workspace default (a deliberate pin). nornir root prints the resolved nornir_home() — "where does nornir keep its data?".
Why no path env-vars
There used to be NORNIR_ROOT (registry), NORNIR_WAREHOUSE_ROOT, and NORNIR_FUNNEL_ROOT. They are gone. The home rule subsumes them: a process inherits its user's $HOME, so there is exactly one place data lives per user and nothing to forget to set in sync.
Containers get the right path the standard way — set the user's $HOME (or run as a user whose passwd home is what you want). No nornir-specific env var is needed or honored.
What's still configurable (and what isn't)
- An absolute
[storage].local_pathinnornir.tomlstill pins a warehouse to an explicit path (e.g. a fast local disk) for the embedded CLI/MCP — that's a deliberate per-config override, not a filesystem-root env-var. - The server has no warehouse override.
NORNIR_WAREHOUSEis no longer read bynornir-server— the server's warehouse is$HOME-derived per-workspace at<home>/.nornir/workspaces/<name>/builds/warehouse. (There is no install flag left that could pin one:install addhas no--warehouse, and both live install paths hand the unit renderer no warehouse — so noEnvironment=NORNIR_WAREHOUSE=line is written; unit tests assert its absence. No generator cleanup is pending — the old-flag wiring was removed in0c240f9/9ea97c4.) - Non-path env-vars the server honors are exactly two:
NORNIR_SERVER_TOKEN(auth) andNORNIR_SERVER_ADDR(bind).NORNIR_SERVERis the client's endpoint.NORNIR_POLL/NORNIR_MONITORare no longer read (poll cadence is the per-workspace registry field; workspaces are registered at runtime).NORNIR_CONFIGremains an embedded-CLI/MCP config-resolution var (the server has no boot config). Behavior vars likeNORNIR_TEST_STALL_SECS,NORNIR_VIZ_*are unaffected.
Erasing it — THE UNINSTALL LAW
nornir install remove — no flags — erases the data root and keeps the identity:
<home>/.nornir/
workspaces/ … ← ERASED by every uninstall
cache/, funnel/, jobs/ … ← ERASED by every uninstall
ssh/ ← KEPT (the deploy key the git remotes have registered)
token ← KEPT (the server bearer token)
(System scope: the key is at <service home>/.ssh, so the uninstall spares that directory and the nornir account that owns it, while erasing the data root and any other state in the home.)
The root is resolved by scope, never hardcoded — the same nornir_home_from($HOME) rule above, with system scope reading the service account's home out of /etc/passwd. Two sibling apps once erased a hardcoded /home/<app> regardless of scope: on a per-user install that path did not exist, so the uninstall wiped nothing and printed success anyway. An eraser and a resolver that disagree about where state lives means one of them is lying.
That is the suite rule (workspace_nordisk/.nornir/suite-uninstall-law.md): uninstall ALWAYS kills the data and ALWAYS keeps the ssh keys — not behind a flag, not opt-in, not --purge. Destroying the identity is the one explicit opt-in --wipe-identity; afterwards a reinstall mints a new key you must register on every remote again. --purge and --keep-identity are deprecated no-ops kept only so old scripts still parse. Deletes run through skidbladnir's guarded primitive systemd::guarded_remove_dir_all, which refuses .ssh components, real users' homes, system directories, and symlinks out of the tree. An uninstall that removed nothing prints NOTHING REMOVED, never success.
Proof
tests/root_resolution.rs::root_is_home_derived_and_same_for_any_user asserts the in-process resolution equals $HOME/.nornir, and — gated on passwordless sudo -n -H -u nornir — runs the built nornir root as the server user and asserts it prints /home/nornir/.nornir. Same code, same rule, two users.