niwaniwa0.1.2
saru-id/niwa
niwaniwa

File locations

Every file niwa reads or writes, where it lives, and whether it is committed. Four roots hold all of it.

Four roots hold everything: the config repo, the state directory, shared data, and a few places elsewhere on the machine. One module resolves all of them, so nothing else in the tool reads HOME or the XDG variables.

The config repo

Everything here is committed. The repo is an ordinary git repository, and niwa init runs git init in it.

~/.config/niwa

init.luauthe entry point: requires each module, then calls niwa.host()
.luaurcstrict mode, and the @niwa and @self aliases
niwa.lockthe resolved versions, committed on purpose
modules/
apps.luau
cli.luau
desktop.luau
dev.luau
inbox.luauwhere an ambiguous proposal lands, permanently
services.luau
shell.luau
system.luau
hosts/
<machine>.luauthis machine only, loaded last
files/sources for niwa.file and niwa.link, named @self/files/…
secrets/
<name>.ageone sealed secret per file
seal-key.agethe passphrase-protected key escrow
state/
<machine>.tomlone stamp per machine, written after every apply

The secrets are committed because they are ciphertext. The key that opens them is not in the repo: it lives in the state directory, and niwa seal-key backup escrows a passphrase-protected copy at secrets/seal-key.age.

state/ is the only directory niwa writes into the repo by itself. Stamps dirty the tree after every apply by design, so the dirtiness check that guards apply --yes excludes them.

The state directory

None of this is committed, and none of it crosses to another machine.

~/.local/state/niwa

archive/
<identity>/
<sha256>displaced bytes, one file per distinct content
cache/
<sha256>prefetched release downloads
apply.lockthe exclusive lock: one apply at a time
baseline.jsonthe drift baseline the watcher's survey learns from
digest.jsonthe weekly upstream digest
journal.jsonacknowledgements, the apply entries, and the declined list
machine-idthe fallback identifier, when the hardware will not give one
run.logthe full output of the commands niwa runs
seal.keythis machine's sealing key
tagsthis machine's tags, written by niwa tag

The journal stays local because the archives beside it hold your bytes. Archives are pruned past ninety days. niwa uninstall leaves this directory alone unless you add --purge.

Every verb that writes takes apply.lock: apply, undo, pull, add, update and uninstall. A plain check takes it when it can, so it cannot save a snapshot over a running apply. plan never takes it, and neither does the watcher's check --notify, so a survey can never block an apply.

Shared data

~/.local/share

mise/
installs/where mise puts the toolchains you declare
niwa/
modules/
<sha256>/shared modules resolved by niwa.use
types/
init.luauthe type definitions your editor reads

niwa init writes the types and niwa uninstall removes them. A shared module is stored under the hash the lockfile pins, so two machines resolving the same niwa.use line read the same bytes.

Elsewhere on the machine

PathWhat is there
~/.local/bin/niwathe binary, where the installer puts it
$ZDOTDIR/.zshrc, or ~/.zshrcone PATH line, carrying the comment that lets uninstall remove exactly it
~/Library/LaunchAgents/rs.niwa.watcher.plistthe watcher's launchd job
~/Library/LaunchAgents/<label>.plistone plist per niwa.service, named after its label
~/Library/LaunchAgents/homebrew.mxcl.<name>.plistread to check a niwa.brew.service
~/Library/Preferences/user preference domains, and one of the watcher's two watch paths
/Library/Preferences/the only place an absolute defaults domain may live
/Library/Managed Preferences/read to find the keys an organization already governs

Where the roots come from

RootDefaultMoved by
home—HOME, which must be set and absolute or niwa stops
the config repo~/.config/niwaXDG_CONFIG_HOME
the state directory~/.local/state/niwaXDG_STATE_HOME
shared data~/.local/shareXDG_DATA_HOME
the Homebrew prefix/opt/homebrew on Apple silicon, /usr/local otherwiseHOMEBREW_PREFIX

An XDG variable is honored when it is set to an absolute path, and ignored otherwise. Each one replaces only the leading half: XDG_STATE_HOME=/tmp/x puts the journal at /tmp/x/niwa/journal.json.

Next

  • File formats — it gives the exact shape of the lockfile, the journal and the stamp
  • Environment variables — five of them move the roots this page names
  • init — it is the verb that writes most of what is listed here

View as markdown

↑↓ navigate · enter opens · esc closes