Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/data-root.md

Data root — <home>/.nornir

TL;DR (the whole thing in four lines)

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:

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)

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.