Skip to content

flakeModule

Nest ships a flakeModule for flake-parts. It wires nest.* module options, evaluates the DOM pipeline, and routes outputs to flake attributes.

flake.nix
{
inputs = {
nest.url = "github:vic/nest";
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "";
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.nest.flakeModules.default ];
# … your modules
};
}

Everything under nest.* except nest.trait and nest.rules is the DOM. You write your topology here:

nest.prod.system = "x86_64-linux";
nest.prod.web = { is = [ nest.host ]; addr = "10.0.0.2"; };

Trait definitions. Each key under nest.trait becomes a trait in the processed trait tree:

nest.trait.host.class.nixos = select: cfg: nixpkgs.lib.nixosSystem { };
nest.trait.server.needs = [ nest.nginx nest.ssh ];
nest.trait.monitoring.neededBy = nest.server;

The rules list. Type: listOf raw (preserves functionArgs metadata for callWithArgs):

nest.rules = [
{ is = nest.host; nixos = { host, ... }: { networking.hostName = host.name; }; }
];

flakeModule passes the processed trait tree + selector constructors as _module.args.nest:

nest = processedTraits // mkSelectors

This makes all traits and selectors available as nest.host, nest.web, nest.attrs { … }, etc. in any module.

After evaluation, config.flake.nest exposes:

config.flake.nest.evalResult.byClass # keyed by className → { nodeName → output }
config.flake.nest.evalResult.outputs # same but flat: { nodeName → output }
config.flake.nest.nestCfg # raw nest.* options
config.flake.nest.processedTraits # injectNames result
config.flake.nest.lib # nest internals (evalNest, etc.)

byClass maps class names to output attrsets. Route them to flake outputs:

# nixosConfigurations
flake.nixosConfigurations = config.flake.nest.evalResult.byClass.nixos or { };
# homeConfigurations
flake.homeConfigurations = config.flake.nest.evalResult.byClass.homeManager or { };
# darwinConfigurations
flake.darwinConfigurations = config.flake.nest.evalResult.byClass.darwin or { };
# Custom output class (e.g., "terranix")
flake.terranix = config.flake.nest.evalResult.byClass.terranix or { };
{
description = "Minimal Nest flake";
inputs = {
nest.url = "github:vic/nest";
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } ({ config, ... }: {
imports = [ inputs.nest.flakeModules.default ];
# Route byClass.nixos to nixosConfigurations
options.flake.nixosConfigurations = inputs.nixpkgs.lib.mkOption { };
config.flake.nixosConfigurations =
config.flake.nest.evalResult.byClass.nixos or { };
# Entity trait
nest.trait.host.class.nixos = select: cfg:
inputs.nixpkgs.lib.nixosSystem {
system = select.node.system;
modules = [ cfg ];
};
# DOM
nest.prod.system = "x86_64-linux";
nest.prod.web = { is = [ nest.host ]; };
# Rules
nest.rules = [
{ is = nest.host;
nixos.networking.hostName = "web"; }
];
});
}

You can use Nest without flake-parts by calling evalNest directly:

flake.nix
outputs = inputs:
let
nestLib = import inputs.nest;
result = nestLib.evalNest {
trait = { host.class.nixos = select: cfg: nixpkgs.lib.nixosSystem { }; };
prod.web = { is = [ nestLib.processedTraits.host ]; };
rules = [ { is = nestLib.processedTraits.host; nixos.networking.hostName = "web"; } ];
};
in {
nixosConfigurations = result.byClass.nixos;
};

Or use the default.nix template which wraps evalModules without flake-parts.

The class name is determined by the entity trait’s class key name:

nest.trait.host.class.nixos = …; # className = "nixos"
nest.trait.home.class.homeManager = …; # className = "homeManager"
nest.trait.server.class.terranix = …; # className = "terranix"

processNode uses firstMatch — the first non-null class fn result wins, and its key name becomes the className for routing.

Contribute Community Sponsor