IOSHI Architecture

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-init command 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.