niwaniwa0.1.2
saru-id/niwa
niwaniwa

Your first config

niwa init writes a starter config that already describes this machine, and this page reads it file by file.

Open a new shell first, so the PATH line the installer wrote is in it.

shell
niwa init

init runs once per machine, and refuses a config that already exists.

What it wrote

It reads the machine first: the formulae Homebrew has on record as requested, the casks it has installed, and this machine's name. Then it writes the whole skeleton, empty rooms included.

~/.config/niwa

init.luauevery module, in the order they run
.luaurcthe aliases your editor reads
niwa.lockthe versions, once something resolves one
modules/
apps.luauthe casks init found
cli.luauthe formulae init found
desktop.luaudock, finder, the desktop
dev.luautoolchains and editors
inbox.luauwhere accepted proposals land
services.luaulaunchd agents, and brew's daemons
shell.luaushell, prompt, terminal
system.luaukeyboard, trackpad, firewall
hosts/
<machine>.luauthis machine only, loaded last
files/sources for the files you declare
secrets/sealed secrets

Three things land outside the repo. The shipped types go to ~/.local/share/niwa/types/init.luau. The watcher is loaded, a launchd job that notifies and never applies. The config directory becomes a git repository on a branch named main.

The files that matter

init.luau is the entry point. It requires each module in turn, and order is execution order. Its last line is niwa.host(), which loads this machine's host file when there is one. A require needs a literal string, so the per-machine lookup goes through that call instead.

.luaurc points your editor at the types.

json
{
  "languageMode": "strict",
  "aliases": {
    "niwa": "~/.local/share/niwa/types",
    "self": "."
  }
}

@niwa is the shipped API, @self is your repo. The same alias works in a require and in a file source, so there is one path concept and not two.

A module is also a group. Output groups by module, and niwa apply --only desktop runs one and leaves the rest as they stand. Two modules setting the same key to different values is an error, and niwa check reports it with both source locations. A host file overriding a module is the supported way for machines to differ: later declaration wins, merged per key.

niwa.lock is committed on purpose. Anything version-resolved records what it resolved to, so a second machine reaches the same versions.

Reading a module

modules/cli.luau carries what the scan found, as one line:

luau
niwa.brew.formula { "fd", "jq", "ripgrep" }

Most of the other modules hold a commented example of their own shape until you write in them. modules/dev.luau is the exception: it declares luau-lsp, so the repo installs the thing that makes editing it pleasant. Read the modules, then commit the repo, because that first commit is what every later change is read against.

Next

View as markdown

↑↓ navigate · enter opens · esc closes