# The config and its modules

The config is a directory of files, and the layout is the only structure
there is. No manifest declares what a module is, and no API call groups
resources. Where a file sits is what it means.

## The layout

`niwa init` does not hand you an empty file. It scans the machine, then
writes a config that already describes it, with the sections you have
not filled in yet labelled and commented. The whole skeleton exists from
the first run, `hosts/` included, because splitting that out later means
rewriting files that already work.

The structure below is the example config the tool's own tests run
against. `state/` is the one directory `init` does not write; the first
apply writes the first stamp.

```tree
label: ~/.config/niwa
root: tests/fixtures/example
```

| Entry | What it is for |
| --- | --- |
| `init.luau` | The entry point. It requires each module in turn, and the order of those lines is execution order. |
| `.luaurc` | The editor and analyzer settings, generated by `init`. |
| `niwa.lock` | What every version-resolved declaration resolved to. Committed. |
| `modules/` | The declarations, split by subject. Each file is also a group. |
| `hosts/` | One file per machine, loaded last, for the differences between them. |
| `files/` | Sources for `niwa.file` and `niwa.link`, referenced as `@self/files/…`. |
| `secrets/` | Sealed secrets, and the escrowed sealing key. Ciphertext only. |
| `state/` | One committed stamp per machine: what it applied, and when. |

The starter config writes eight modules: `cli`, `apps`, `shell`, `dev`,
`desktop`, `system`, `services`, and `inbox`. The names are a starting
point, not a schema. Rename them, split them, or add your own, and the
only line that has to know is the `require` in `init.luau`.

`inbox.luau` is worth naming. An accepted proposal lands there when no
other module is a clear match, and it is a permanent home rather than a
queue. Moving a line out of it later is optional.

## The entry point, and why `niwa.host()` exists

`init.luau` requires each module by a literal path, so the analyzer can
resolve every one of them before anything runs. That is also why the
last line is a call and not another `require`:

```luau
niwa.host() -- hosts/<this machine>.luau, if it exists, loaded last
```

A machine's own file has a name known only at run time, which a static
`require` cannot express. `niwa.host()` performs that lookup through
niwa's own resolver, so the dynamic case stays typed and the static case
stays analysable.

## Modules are also groups

Because a module is a file, the tool can talk about it. On a converged
machine `niwa plan -v` prints one count per module, and `-vv` lists
every resource under the module that declared it.
`niwa apply --only desktop` runs one module and leaves the rest as they
stand. A module is a unit of reading, a unit of reporting, and a unit of
running, and you got all three by naming a file.

## Modules declare, hosts override

A host file overriding a module is the supported mechanism for
per-machine variance: later declaration wins, merged per key. Two
modules setting one key to different values is a lint error with both
source locations, because there the order is an accident.
[Many machines](/concepts/machines) has the worked pair.

## Why there is no profile system

The config is a program, so the composition story is already answered.

```luau
local function tool(name: string, config: string?)
  local formula = niwa.brew.formula(name)
  if config then
    niwa.file(`~/.config/{name}/{config}`, { source = `@self/files/{config}` })
  end
  return formula
end

tool("starship", "starship.toml")
tool("btop")
```

That function is a bundle. A module is a role. A shared recipe is a file
that returns a function. A bundle primitive, a profile system, and a
recipe format would be three concepts covering what one already covers,
each with its own syntax, its own error messages, and its own page here.

Results pass through untouched, so `tool("neovim").changed` branches
exactly like the call it wraps. Sugar you write and sugar niwa ships are
the same kind of thing, which is why the built-in surface stays small.
