IOSHI Architecture

IOSHI separates a personal machine into three concerns. The point is not tidiness — it is that every question about the machine has one obvious place to look.

ioshi/
├── i-intelligence/   # the workspace: Emacs, compositor, theming
└── os-system/        # the substrate: base, firstboot
                      # hi-hardware/ — the machine and its network: not here

The distribution owns two of the three. The third is deliberately absent, and that absence is the most important thing on this page.

The layers are ordered by how often they change. The intelligence layer is edited constantly; the substrate rarely; the hardware layer, which lives in your flake rather than this one, almost never.

1. i — intelligence interface

The largest layer, 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, a terminal, the shell, theming, and around thirty Home Manager modules covering everything from git config to media tooling.

Modules here gate on feature flags, so the same layer produces a full desktop on one host and a terminal-only environment on another.

This layer 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 is exposed and imported individually rather than by pulling in the directory.

The three concerns are descriptive, not enforced. They say what a piece of config is about, not which module system delivers it. Nothing checks the boundary and nothing is meant to.

2. os — operating system

Deliberately minimal. Two files:

  • 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.
  • 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.

There is no desktop.nix and no server.nix. Audio, Bluetooth, printing, containers and bootloaders are facts about a particular machine, so they belong to the flake that knows which machine it is describing.

3. hi — hardware / internet

Not in this repository, on purpose.

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 concerns 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 i-intelligence
  • The substrate is touched almost never, which is how you want it

When a change needs edits in two layers 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 layers gate on.