The module system is the foundation of Isabel’s Dotfiles. Modules are organized by system class and concern, allowing you to build complex configurations from small, focused components.
Module hierarchy
Modules are organized in a hierarchical structure under modules/:
modules/
├── flake/ # Flake outputs and development environment
├── generic/ # Cross-platform generic modules
├── base/ # Shared between NixOS and macOS
├── nixos/ # NixOS-specific modules
├── darwin/ # macOS-specific modules
├── home/ # Home Manager user environment
├── wsl/ # WSL2-specific modules
└── iso/ # ISO installation image modules
Module inheritance
Modules inherit from each other based on system class:
Generic modules
modules/generic/ - Platform-agnostic options like profiles and packages
Base modules
modules/base/ - Imports generic and home modules, adds Nix configuration, fonts, and users
Class modules
modules/nixos/ or modules/darwin/ - Import base modules and add platform-specific configuration
Host configuration
systems/<hostname>/ - Imports class modules and sets host-specific options
Module categories
Flake modules
Located in modules/flake/, these modules define flake outputs:
{
imports = [
../../systems # System definitions
./args.nix # Base args passed to the flake
./checks # Custom validation checks
./lib # Shared library functions
./packages # Custom packages
./programs # Dev shell programs
];
}
These modules don’t configure systems directly - they generate flake outputs like packages, checks, and development shells.
Generic modules
Located in modules/generic/, these work with any module system:
{
imports = [
./packages.nix # Package declarations
./profiles.nix # Profile options (graphical, workstation, etc.)
];
}
Example profile options:
{ lib , ... }:
let
inherit ( lib ) mkEnableOption ;
in
{
options . garden . profiles = {
graphical . enable = mkEnableOption "Graphical interface" ;
headless . enable = mkEnableOption "Headless" ;
workstation . enable = mkEnableOption "Workstation" ;
laptop . enable = mkEnableOption "Laptop" ;
server . enable = mkEnableOption "Server" ;
};
}
Base modules
Located in modules/base/, these are shared between NixOS and macOS:
{
imports = [
../generic # Generic cross-platform modules
../../home # Home Manager configuration
./nix # Nix package manager settings
./nixpkgs.nix # Nixpkgs configuration
./programs.nix # Base programs
./system # System-level configuration
./users # User management
];
}
Key base modules:
nix/ Nix daemon configuration, substituters, plugins, and environment variables
users/ User account management with garden.system.users and garden.system.mainUser
system/ System-wide settings like fonts, revision tracking, and environment variables
nixpkgs.nix Nixpkgs configuration including allowed unfree packages and overlays
NixOS modules
Located in modules/nixos/, these configure NixOS-specific features:
{
_class = "nixos" ;
imports = [
../base # Import base modules
./boot # Boot loader and kernel
./catppuccin.nix # Theme integration
./emulation.nix # Virtualization and emulation
./environment # Environment variables
./extras.nix # Additional module imports
./hardware # Hardware configuration
./headless.nix # Headless system configuration
./kernel # Kernel configuration
./networking # Network configuration
./programs # NixOS programs
./security # Security settings
./services # System services
./system # System configuration
./users # NixOS user configuration
];
}
Example boot module structure:
# modules/nixos/boot/default.nix
{
imports = [
./generic.nix # Generic boot configuration
./loader.nix # Boot loader selection
./secure-boot.nix # Secure boot with lanzaboote
];
}
Darwin modules
Located in modules/darwin/, these configure macOS systems:
darwin/
├── brew/ # Homebrew integration
├── config-path.nix # Configuration paths
├── default.nix # Main entry point
├── hardware/ # Hardware settings
├── preferences/ # macOS preferences
└── security/ # Security configuration
Home Manager modules
Located in modules/home/, these configure the user environment:
{
_class = "homeManager" ;
imports = [
../generic # Generic modules
./docs.nix # Documentation settings
./environment # User environment variables
./extras.nix # External module imports
./home.nix # Home Manager settings
./profiles.nix # User profiles
./programs # User programs
./secrets.nix # User secrets
./themes # Application themes
];
}
Home Manager modules are imported by base modules, so they’re available to all system classes.
Module structure
Most module directories follow this pattern:
{
imports = [
./feature-a.nix
./feature-b.nix
./feature-c.nix
];
}
User management
The base user modules provide a unified interface for declaring users:
# modules/base/users/options.nix
{ lib , config , ... }:
{
options . garden . system = {
mainUser = mkOption {
type = enum config . garden . system . users ;
description = "The username of the main user for your system" ;
default = builtins . elemAt config . garden . system . users 0 ;
};
users = mkOption {
type = listOf str ;
default = [ "isabel" ];
description = '''
A list of users that you wish to declare as your non-system users.
The first username in the list will be treated as your main user
unless garden.system.mainUser is set.
''' ;
};
};
}
Per-user configuration:
# modules/base/users/isabel.nix
{ lib , config , ... }:
let
inherit ( lib ) elem mkIf ;
in
{
config = mkIf ( elem "isabel" config . garden . system . users ) {
users . users . isabel = {
openssh . authorizedKeys . keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQDiHbMSinj8twL9cTgPOfI6OMexrTZyHX27T8gnMj2"
];
};
};
}
Writing modules
Follow these guidelines when writing modules:
Module function signature
Use a tree-like structure for the head lambda args only if needed:
{
lib ,
pkgs ,
config ,
inputs ,
...
}:
{
# Module configuration
}
Import structure
Avoid going backwards in the flake’s file structure:
imports = [
../generic # Parent directory
../../home # Ancestor directory
./local-module.nix # Same directory
] ;
Option namespacing
All custom options should be under the garden.* namespace:
options . garden . myFeature = {
enable = mkEnableOption "My feature" ;
package = mkPackageOption pkgs "my-package" { };
settings = mkOption {
type = types . attrs ;
default = { };
};
} ;
Conditional configuration
Use mkIf for conditional config based on options:
let
cfg = config . garden . myFeature ;
in
{
options . garden . myFeature = {
enable = mkEnableOption "My feature" ;
};
config = mkIf cfg . enable {
# Only applied when enabled
};
}
Module discovery
Modules are discovered through explicit imports:
System class determines the entry point (e.g., modules/nixos/default.nix)
Each default.nix imports its submodules
Imports cascade down the module tree
There’s no automatic module discovery - you must explicitly import new modules in a default.nix file.
Next steps
System definitions Learn how to configure individual hosts using modules
Architecture overview Review the overall architecture