Architecture¶
How nix-wire auto-wires your flake outputs from directory structure.
Overview¶
nix-wire is a thin layer over flake-parts. The entry
point is mkFlake (in lib/default.nix), which calls
flake-parts.lib.mkFlake with a pre-wired configuration that scans your
project directories and generates the appropriate flake attributes.
The core engine is the wireGeneric function (in lib/utils.nix) - a
generic directory walker that collects .nix files and directories with
default.nix into an attribute set. Every auto-wiring function is a
specialization of wireGeneric with a different buildFn.
mkFlake (lib/default.nix)
└─ flake-parts.lib.mkFlake
├─ flake.nixosConfigurations ← mkNixosConfigs ← wireGeneric
├─ flake.darwinConfigurations ← mkDarwinConfigs ← wireGeneric
├─ flake.nixosModules ← wireModules ← wireGeneric
├─ flake.darwinModules ← wireModules ← wireGeneric
├─ flake.homeModules ← wireModules ← wireGeneric
├─ flake.flakeModules ← wireModules ← wireGeneric
├─ flake.overlays ← wireOverlays ← wireGeneric
├─ flake.templates ← wireTemplates ← wireGeneric
└─ perSystem:
├─ packages ← wirePackages ← wireGeneric
│ ← mkIsoPackages ← wireGeneric
├─ devShells ← wirePackages ← wireGeneric
└─ legacyPackages.homeConfigurations ← mkHomeConfigs ← wireGeneric
wireGeneric - the engine¶
This is the heart of nix-wire. It scans a directory and builds an attribute set from what it finds:
- Reads the directory safely (returns
{}if it doesn't exist) - Applies the precedence rule - if both
foo.nixandfoo/default.nixexist,foo.nixis filtered out andfoo/default.nixwins - Iterates over every entry:
- If it's a directory with
default.nix(or matchesisDirAccepted): adds{ ${name} = buildFn (dir/name) name; } - If it's a
.nixfile: adds{ ${stripNix name} = buildFn (dir/name) (stripNix name); } - Otherwise: skips it
- Returns the merged attribute set
The buildFn receives two arguments: the path to the file (or
default.nix) and the name (entry name without .nix, or directory
name). This lets each specialization know what it's processing - e.g.,
hostnames, package names, usernames.
Precedence: directory over file¶
The filterPreferDir function ensures that when both foo.nix and
foo/default.nix exist in the same directory, foo/default.nix is used and
foo.nix is silently dropped. This lets you start with a flat file and
upgrade to a directory later without renaming.
mkNixosConfigs¶
Scans hosts/nixos/ and creates nixosConfigurations.<hostname> for each
entry. For every host, it assembles a module list:
modules = commonModules("nixos", home, path, dir, hostname)
++ [{ networking.hostName = mkDefault hostname; }]
commonModules breakdown¶
commonModules builds the shared module list for every NixOS/Darwin host:
commonModules = type: home: path: dir: hostname:
[
path # ← the host's own config file
({ pkgs, ... }: {
imports = homeModules; # ← home-manager modules (if home=true)
users.users = getUsers dir hostname pkgs; # ← auto-discovered users
})
commonNix # ← allowUnfree, experimental-features, etc.
]
When home = true, homeModules expands to commonHomeModules, which:
- Imports
home-manager.nixosModules.home-manager - Sets
useGlobalPkgs,useUserPackages,backupFileExtension(allmkDefault) - Wires
home-manager.usersfrom the host'susers/subdirectory viagetUsersHome - Sets
extraSpecialArgsto{ inherit inputs; flake = inputs.self; } - Adds
sharedModuleswith Darwin-specificsessionPathon macOS
mkDarwinConfigs¶
Identical to mkNixosConfigs but wraps with nix-darwin.lib.darwinSystem
instead of nixpkgs.lib.nixosSystem. Uses home-manager.darwinModules.home-manager
for Home Manager integration.
mkIsoPackages¶
Builds ISO image derivations as per-system packages. Key design choices:
- Arch-aware - only evaluates on
-linuxsystems (Darwin builds are skipped vialib.optionalAttrs) - Per-system, not flake-level - ISOs live in
packages.<system>.<name>, not in a separateisoConfigurationsattribute. This meansnix build .#rescueautomatically produces a native-arch ISO for the current machine. - Inspectable - the full NixOS evaluation is attached as
passthru.config, sonix eval .#packages.x86_64-linux.rescue --apply 'x: x.passthru.config...'works without a separate flake attribute. - Installer profile - automatically imports
installation-cd-minimal.nixfrom nixpkgs'modulesPath, giving you a bootable ISO. Override withinstallerModuleto use a different profile.
The module assembly uses isoModules, a DRY helper:
isoModules = home: path: dir: name: installerModule:
commonModules("nixos", home, path, dir, name)
++ [ ({ modulesPath, ... }: {
imports = [ "${modulesPath}/installer/cd-dvd/${installerModule}" ];
})
{ networking.hostName = mkDefault name; }
]
mkHomeConfigs¶
Scans hosts/home/ for standalone Home Manager configurations - users
not tied to a specific NixOS/Darwin host. Each entry becomes a
homeManagerConfiguration with:
usernameandhomeDirectoryset automatically from the filenamehomeDirectoryresolves to/Users/<name>on Darwin,/home/<name>on Linuxnix.packageset topkgs.nixextraSpecialArgswithinputsandflake
Results land in legacyPackages.homeConfigurations (per-system, since pkgs
varies by platform).
wireModules¶
The simplest specialization - buildFn just returns the path itself. Scans
modules/nixos/, modules/darwin/, modules/home/, modules/flake/ and
maps each entry name to its file path. These become nixosModules,
darwinModules, homeModules, and flakeModules respectively.
wireOverlays¶
Scans overlays/ and imports each overlay with commonSpecialArgs
({ inherit inputs; flake = inputs.self; }). The imported overlay function
receives these args in addition to final: prev.
wirePackages¶
Scans a packages (or devshells) directory and applies pkgs.callPackage to
each entry. Used for both packages/ → packages and devshells/ →
devShells.
wireTemplates¶
Scans templates/ and maps each subdirectory to a template object
{ path = ...; description = ...; }. Uses a custom isDirAccepted that
accepts any directory (not just those with default.nix). An optional
template.nix inside the directory provides { description = "..."; };
otherwise the directory name is used as the description.
User discovery¶
mkUsers¶
Generic user collector. Looks for users/ inside a host directory and walks
it with wireGeneric. Each user file/directory becomes an entry.
getUsers¶
Creates users.users.<name> entries with a default home path:
/Users/<name> on Darwin, /home/<name> on Linux.
getUsersHome¶
Creates home-manager.users.<name> entries that import each user's config
file. This is what wires per-user Home Manager configs into hosts.
Special args¶
Every host module and Home Manager module receives:
inputs- your full flake inputs attrsetflake- alias forinputs.self(so you can writeflake.homeModules.shell)
Common Nix settings¶
All hosts get these defaults (all via mkDefault, so you can override):
commonNix = {
nixpkgs.config.allowUnfree = mkDefault true;
nixpkgs.overlays = attrValues inputs.self.overlays;
nix.settings.max-jobs = mkDefault "auto";
nix.settings.experimental-features = mkDefault "nix-command flakes";
};
autoImport and autoImportExcept¶
These are pure builtins utilities - they work in any evaluation context
(flakes, modules, repl). They return a list of paths (not an attrset),
suitable for use in imports lists.
autoImport- imports all sibling.nixfiles and dirs withdefault.nix(skipsdefault.nixitself)autoImportExcept- same, but also skips names in the exclusions list
These are exported from the flake as inputs.nix-wire.lib.autoImport and
inputs.nix-wire.lib.autoImportExcept for use outside of the mkFlake
wiring - e.g., in module files where you want to auto-import siblings.