A composable Nix flake library for managing NixOS, nix-darwin, Home Manager, and nix-on-droid system configurations. Inspired by easy-hosts, flake-hosts provides a clean filesystem-based approach to organizing multiple host configurations with flake-parts integration.
- Multi-platform support: NixOS, macOS (nix-darwin), Home Manager, and Nix-on-Droid
- Filesystem-based discovery: Automatically discover hosts from your directory structure
- Flexible configuration: Mix auto-discovered and explicitly defined hosts
- Class-based organization: Apply configurations per system class (nixos, darwin, etc.)
- Architecture-specific configs: Per-architecture configuration support
- Input-less architecture: Consumer flakes control their own input versions
- Flake-parts integration: Native flake-parts module for easy integration
Add flake-hosts to your flake inputs:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-hosts.url = "github:your-repo/flake-hosts";
};
}outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.flake-hosts.flakeModule ];
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
flake-hosts = {
# Automatic host discovery
auto = {
enable = true;
hostsDir = ./hosts; # Required when auto.enable = true
systems = [ "x86_64-linux" ]; # Optional: filter systems to build
};
# Explicit host definitions
hosts = {
# Shared configuration for explicit hosts
default = {
modules = [ ./modules/shared.nix ];
};
# Individual host
server = {
class = "nixos";
arch = "x86_64";
modules = [ ./hosts/server/hardware.nix ];
};
};
# Per-class configuration
perClass = class: {
modules = [
"${inputs.self}/modules/${class}/default.nix"
];
};
# Per-architecture configuration
perArch = arch: {
modules = [
# Architecture-specific modules
];
};
};
};# Show available outputs
nix flake show
# Try the comprehensive example
cd examples/simple && nix flake show
# Try the minimal example
cd examples/minimal && nix flake show
# Validate configurations
nix flake checkWhen auto.enable = true, flake-hosts automatically discovers hosts from your filesystem:
flake-hosts.auto = {
enable = true;
hostsDir = ./hosts; # Required: directory containing host files
systems = [ "x86_64-linux" ]; # Optional: filter systems to build
};Organize your hosts using either approach:
hosts/
├── default.nix # Shared config for auto-discovered hosts
├── server.nix # Host as .nix file (hostname = filename)
├── desktop/ # Host as directory (hostname = directory name)
│ └── default.nix # Host configuration
└── laptop/
└── default.nix
Important: Only directories containing default.nix are treated as valid hosts. Directories without default.nix are ignored.
Each host file should export a configuration:
# hosts/server.nix
{
class = "nixos"; # System class
arch = "x86_64"; # Architecture
pure = false; # Whether to skip shared config merging
modules = [ # Host-specific modules
./server-hardware.nix
./server-services.nix
];
specialArgs = { # Special arguments passed to modules
hostName = "server";
};
}Use perClass to apply configuration based on system class:
perClass = class: {
modules = [
# Load class-specific modules
"${inputs.self}/modules/${class}/default.nix"
];
specialArgs = {
isNixOS = class == "nixos";
};
};Create class-specific modules:
modules/
├── nixos/
│ └── default.nix # NixOS-specific configuration
├── darwin/
│ └── default.nix # macOS-specific configuration
├── home/
│ └── default.nix # Home Manager configuration
└── nixOnDroid/
└── default.nix # Nix-on-Droid configuration
Use perArch for architecture-specific configuration:
perArch = arch: {
modules =
if arch == "x86_64" then [ ./modules/x86_64.nix ]
else if arch == "aarch64" then [ ./modules/aarch64.nix ]
else [];
};auto.enable(boolean, default: false): Enable automatic host discoveryauto.hostsDir(path, required when auto.enable = true): Directory containing host configurationsauto.systems(list of strings, optional): Filter to only build specified systems
hosts(attrset): Explicitly defined host configurationshosts.default(attrset): Shared configuration for explicit hosts
perClass(function): Function taking class name, returning configuration for that classperArch(function): Function taking architecture, returning configuration for that arch
Each host can specify:
class(enum): System class - "nixos", "darwin", "home", or "nixOnDroid"arch(string): Architecture - "x86_64", "aarch64", "armv6l", "armv7l", "i686", etc.system(string, internal): Full system string, automatically computedpure(boolean, default: false): Skip shared configuration mergingdeployable(boolean, default: false): Mark host as deployablemodules(list): NixOS/Darwin/Home Manager modulesspecialArgs(attrset): Special arguments passed to modules
Per-host input overrides:
nixpkgs: Override nixpkgs input for this hostnix-darwin: Override nix-darwin input for darwin hostshome-manager: Override home-manager input for home hostsnixOnDroid: Override nixOnDroid input for nix-on-droid hosts
flake-hosts supports four system classes:
| Class | Builder | Output Collection |
|---|---|---|
nixos |
nixpkgs.lib.nixosSystem |
nixosConfigurations |
darwin |
nix-darwin.lib.darwinSystem |
darwinConfigurations |
home |
home-manager.lib.homeManagerConfiguration |
homeConfigurations |
nixOnDroid |
nixOnDroid.lib.nixOnDroidConfiguration |
nixOnDroidConfigurations |
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-hosts.url = "github:your-repo/flake-hosts";
};
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.flake-hosts.flakeModule ];
systems = [ "x86_64-linux" ];
flake-hosts.auto = {
enable = true;
hostsDir = ./hosts;
};
};
}# hosts/server.nix
{
class = "nixos";
arch = "x86_64";
modules = [
({ config, pkgs, ... }: {
system.stateVersion = "24.05";
environment.systemPackages = [ pkgs.vim ];
})
];
}# flake.nix
flake-hosts = {
auto = {
enable = true;
hostsDir = ./hosts;
systems = [ "x86_64-linux" "aarch64-darwin" ];
};
hosts = {
default.modules = [ ./modules/shared.nix ];
};
perClass = class: {
modules = [ "${inputs.self}/modules/${class}/default.nix" ];
};
perArch = arch: {
modules = lib.optionals (arch == "aarch64") [ ./modules/apple-silicon.nix ];
};
};# modules/nixos/default.nix
{ config, pkgs, ... }: {
system.stateVersion = "24.05";
environment.systemPackages = with pkgs; [
vim
git
htop
];
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
}# modules/darwin/default.nix
{ config, pkgs, ... }: {
environment.systemPackages = with pkgs; [
vim
git
htop
];
services.nix-daemon.enable = true;
homebrew = {
enable = true;
brews = [ "curl" "wget" ];
};
}# Build a NixOS system
nix build .#nixosConfigurations.server.config.system.build.toplevel
# Build a Darwin system
nix build .#darwinConfigurations.macbook.system
# Build a Home Manager configuration
nix build .#homeConfigurations.user.activationPackage# Switch to configuration
sudo nixos-rebuild switch --flake .#server
# Test configuration
sudo nixos-rebuild test --flake .#server# Switch to configuration
darwin-rebuild switch --flake .#macbook- Separate concerns: Keep hardware, software, and user configurations in separate modules
- Use shared modules: Extract common configuration into shared modules
- Leverage class modules: Use
perClassfor system-type-specific configuration - Keep hosts minimal: Host files should primarily import and compose modules
- Never commit secrets: Use tools like sops-nix or agenix for secrets management
- Validate paths: Don't use
${inputs.self}paths inauto.hostsDir(causes infinite recursion) - Review configurations: Use
nix flake checkto validate configurations
- Filter systems: Use
auto.systemsto build only needed configurations - Use pure hosts: Set
pure = truefor hosts that don't need shared configuration - Optimize imports: Import only necessary modules per host
Error: "flake-hosts.auto.hostsDir is required when auto.enable = true"
- Solution: Set
auto.hostsDir = ./hosts;when using auto-discovery
Error: "Cannot reference the flake itself"
- Problem: Using
${inputs.self}/pathinauto.hostsDir - Solution: Use relative paths like
./hostsinstead
Host not discovered
- Check: Directory must contain
default.nixfile - Check: Directory name matches expected hostname
- Check: File is not in reserved names (
default,default.nix)
Module evaluation errors
- Check: All imported modules exist and are valid
- Check: Class modules exist for all used classes
- Use:
nix flake checkto validate configuration
# Show discovered hosts
nix flake show
# Check for evaluation errors
nix flake check
# Evaluate specific configuration
nix eval .#nixosConfigurations.hostname.config.system.name
# Show derivation details
nix show-derivation .#nixosConfigurations.hostname.config.system.build.toplevelOld approach:
flake-hosts.auto = {
enable = true;
hostsDir = ./hosts;
modulesDir = ./modules; # Removed
};New approach:
flake-hosts = {
auto = {
enable = true;
hostsDir = ./hosts;
};
perClass = class: {
modules = [ "${inputs.self}/modules/${class}/default.nix" ];
};
};- tgirlcloud/easy-hosts - Original inspiration
- ehllie/ez-configs - Similar approach
- hercules-ci/flake-parts - Flake composition framework
Contributions are welcome! Please:
- Check existing issues and PRs
- Follow the existing code style
- Add tests for new features
- Update documentation as needed
This project is licensed under the MIT License - see the LICENSE file for details.