Feature and Host Modules
How features span configuration classes, how hosts assemble them, the flake.modules storage pattern, and how shared top-level options replace specialArgs. These correspond to Steps 5-6 of the conversion in SKILL.md.
Feature Modules
Feature modules set options across multiple configuration classes.
admin.nix - Cross-platform admin privileges:
{ config, ... }:
{
flake.modules = {
# NixOS: add user to wheel
nixos.admin = {
users.groups.wheel.members = [ config.username ];
};
# Darwin: set primary user
darwin.admin = {
system.primaryUser = config.username;
};
};
}shell.nix - Cross-platform shell configuration:
{ config, lib, ... }:
{
flake.modules = {
nixos.shell = nixosArgs: {
programs.fish.enable = true;
users.users.${config.username}.shell = nixosArgs.config.programs.fish.package;
};
homeManager.shell = {
programs.fish.enable = true;
programs.starship.enable = true;
};
};
}Host Configuration Modules
myhost.nix - Specific host definition:
{ config, ... }:
let
inherit (config.flake.modules) nixos homeManager;
in
{
configurations.nixos.myhost.module = {
imports = [
nixos.admin
nixos.shell
# Hardware-specific imports
];
nixpkgs.hostPlatform = "x86_64-linux";
system.stateVersion = config.stateVersion;
# Host-specific config
networking.hostName = "myhost";
};
}The flake.modules Pattern
flakeModules.modules from flake-parts provides the flake.modules option for storing lower-level modules grouped by module class (nixos, darwin, homeManager). It is a convenience - when the structure of your lower-level modules is itself part of your system model, prefer declaring your own options instead (see the Anti-patterns section in SKILL.md):
# In any module file
{ config, ... }:
{
flake.modules = {
nixos.featureName = { ... }: {
# NixOS module content
};
homeManager.featureName = { ... }: {
# home-manager module content
};
darwin.featureName = { ... }: {
# nix-darwin module content
};
};
}Usage in host configs:
{ config, ... }:
let
inherit (config.flake.modules) nixos homeManager;
in
{
configurations.nixos.myhost.module = {
imports = [ nixos.featureName nixos.otherFeature ];
};
}deferredModule Semantics
deferredModule stores modules unevaluated - the target module system (NixOS, home-manager, nix-darwin) pays the evaluation cost when it imports the value. Semantics worth knowing when debugging:
- Options inside a deferred module are invisible to the outer flake-parts system. Outer modules cannot read them,
debug.optionswill not list them, and type errors surface only when the host system evaluates - debug deferred module content at the host level, not in the flake-parts repl. - Multiple definitions of the same deferredModule option merge by concatenating
imports- that is why several feature modules can each contribute to oneconfigurations.nixos.<host>.module(or any shared option) and compose cleanly. lib.types.deferredModuleWith { staticModules = ...; }is the escape hatch when the outer system needs visibility - the static modules’ options become visible in the outer system’s documentation.flake.modulesis class-typed. Values are wrapped with_class, so loading a module under the wrong class (e.g. ahomeManagermodule intonixosSystem) fails as a type error in class-aware evaluators rather than silently misconfiguring.
import-tree Details
The import-tree flake auto-imports all .nix files recursively:
outputs = inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; }
(inputs.import-tree ./modules);Behavior:
- Imports all
*.nixfiles under./modulesrecursively - Paths containing
/_are ignored by default - Each file becomes a flake-parts module
Ignoring files:
modules/
_templates/ # Ignored (starts with _)
_wip.nix # Ignored
feature.nix # ImportedSharing Values Without specialArgs
Anti-pattern (traditional):
# Passing values via specialArgs
lib.nixosSystem {
specialArgs = { inherit username scripts; };
modules = [ ./configuration.nix ];
}Dendritic approach:
# meta.nix - declare top-level option
{ lib, ... }:
{
options.username = lib.mkOption {
type = lib.types.str;
default = "myuser";
};
}
# feature.nix - read from top-level config
{ config, ... }:
{
flake.modules.nixos.myFeature = {
users.users.${config.username}.extraGroups = [ "docker" ];
};
}Complete Example Structure
flake.nix
flake.lock
modules/
# Infrastructure
systems.nix
flake-parts.nix
meta.nix
nixos.nix
darwin.nix
home-manager.nix
# Features (cross-platform)
shell.nix
admin.nix
git.nix
editor.nix
# Hosts
desktop.nix
laptop.nix
server.nix
# Platform-specific features
linux/
gaming.nix
docker.nix
darwin/
homebrew.nix