IOSHI Architecture
Table of Contents
IOSHI is how a personal machine is reasoned about: three concerns — i, os and hi. The point is not tidiness — it is that every question about the machine has one obvious place to look.
It is no longer how the repository is laid out. The directory split is by module system, because that is the one fact that decides how a file is imported:
home/ # the Home Manager tier: the settings of one user
modules/ # the NixOS tier: the settings of the machine
There used to be an ioshi/ tree — i-intelligence/, os-system/ and
hi-hardware/. It did not partition anything: i-intelligence/ held 48 of the
53 files, and hi-hardware/ held no hardware fact at all. The three names were a
true story about what config is about, dressed up as a directory contract — and
the consuming flake used the same three words for its own tree, which made every
path in both repositories ambiguous. The story stayed; the directories went.
The distribution owns the capability of all three concerns, at different levels. What it never owns is a fact about your machine.
The tiers are ordered by how often they change. The Home Manager tier is edited constantly; the NixOS tier rarely; the machine facts, which live in your flake rather than this one, almost never.
1. home/ — the Home Manager tier
The largest tier, and the one that owns the user's actual working day: Emacs (a pgtk build with a curated package set and modal editing), the EWM compositor's user half, a terminal, the shell, theming, and fifteen Home Manager modules covering everything from git config to media tooling.
Modules here gate on feature flags, so the same tier produces a full desktop on one host and a terminal-only environment on another.
This tier is mostly Home Manager, but not exclusively. Its default.nix is
a Home Manager aggregate and importing it at system level fails to evaluate.
Where a system-level module is genuinely needed — ewm.nix is the case, since
a compositor needs a system service — it lives in modules/ and is exposed as
nixosModules.ewm, which a consumer that wants a desktop imports explicitly
rather than inheriting from a role it did not choose.
The two tiers are split by module system, not by subject. Where a piece of config belongs is decided by how it is imported. What it is about is the IOSHI story above, and nothing enforces it.
2. modules/ — the NixOS tier
Deliberately minimal. Six modules, three of them the substrate:
- base — user and shell, timezone, locale, console keymap, Nix experimental
features, store optimisation, and garbage collection. Locale, timezone and
keymap are all
mkDefault, so a consumer overrides them without a fight. - init — the first system generation, and the
emanix-initcommand on PATH. - firstboot — packages a one-time setup command, shellchecked at build time
so a broken script fails the build rather than the install. The distribution
owns the convention; you supply the script through
emanix.firstboot.text. - gpu — one option for the graphics hardware,
emanix.hardware.gpu, defaulting to null. - firmware — the redistributable-firmware default.
- ewm — the compositor service, exposed as
nixosModules.ewm.
There is no desktop.nix and no server.nix. Audio, Bluetooth, printing
and bootloaders are facts about a particular machine, so they belong
to the flake that knows which machine it is describing. Containers are the
exception: base.nix enables Docker for every host, as a default a consumer may
override.
3. hi — hardware / internet
Capability, not facts. The distribution ships emanix.hardware.gpu and the
redistributable-firmware default, which is all a distribution can know: how to
bring up a GPU, not which GPU a box has.
Which drivers must be in the initrd, how the disks are partitioned, what mesh or network the host joins, which printer is in the room — these are facts about a machine. A distribution can know how to enable them. It cannot know which machines want them, so it should not decide.
This is the same argument that removed the roles, applied one level up. It is also why Tailscale is not here: a mesh VPN is worth having, but choosing one and wiring it to a particular coordination server is your decision, not the distribution's. Bring your own; NixOS makes it a few lines.
4. How a host is composed
lib/mkHost is the composer. It takes:
{ hostName, role, username, hardware ? null, extraModules ? [ ], homeModules ? [ ] }
and returns a nixosSystem built from the distribution core (emanix.nix),
the hostname, the username and role, the package overlay, the secret store and
Home Manager wiring — then your hardware module if you passed one, and
anything else in extraModules.
Three details are worth dwelling on.
The hostname is a default, not a fixed value. One platform cannot tolerate the
system setting its own hostname at activation: under WSL it breaks the user
session bootstrap. Making it mkDefault lets that host force it empty without
forking the composer.
role selects nothing. There were once profiles/roles/{workstation,server,wsl}.nix
and role chose between them. They were deleted on 2026-08-30, because by the
end they differed in almost nothing and what they carried was host shape rather
than distribution policy. role survives purely as metadata: it is recorded
on emanix.role, exported as EMANIX_ROLE by the shell, and interpreted by
whoever consumes it. A label, not a dispatch.
The two arguments mkHost was called with are not mkDefault. Everything else
the composer contributes is an opinion a consumer may outrank. username and
role are not opinions — they are the inputs. Overriding role to something
other than what mkHost was called with yields an incoherent host, with modules
branching on one answer while the system was composed under another. Role
output is an opinion; mkHost input is an invariant.
Use homeModules for the user's Home Manager config rather than reaching into
home-manager.users.<name> from extraModules. mkHost already knows the
username, and spelling it again in the consumer is how the two drift apart.
5. Why the split holds
The tiers cut along real seams:
- A new machine touches only your flake's hardware layer
- A new shape of machine touches only your flake — the distribution has one shape and needs no edit
- A new tool touches only
home/ - The NixOS tier is touched almost never, which is how you want it
When a change needs edits in two tiers at once, that is a signal the boundary is wrong — not that the architecture is inconvenient.
See Adding a Host for the procedure, and Options for the flags the tiers gate on.