skip to content

Reusable Features

A web server needs nginx, and every server needs ssh so operators can log in. Rather than write that into each host, you define each concern once as a feature and let hosts pick the features they need. The webserver feature includes ssh, so a host that picks webserver gets both.

In gen a feature is an aspect: a named bundle of configuration, with its content grouped by the class it targets, such as nixos.

let
gen = inputs.gen.lib.mkGenLibs { };
m = gen.merge;
cnf.keySemantics.nixos.category = "class";
aspectSchema = gen.aspects.mkAspectSchema cnf;
fleet = m.evalModuleTree { } [
(aspectSchema.mkAspectModule { })
{ options.hosts = m.mkOption { type = m.types.attrsOf m.types.raw; default = { }; }; }
{
aspects.ssh.nixos.services.openssh.enable = true;
aspects.monitoring.nixos.services.prometheus.exporters.node.enable = true;
aspects.webserver.includes = [ "ssh" ];
aspects.webserver.nixos.services.nginx.enable = true;
hosts.web-1.aspects = [ "webserver" ];
hosts.db-1.aspects = [ "ssh" "monitoring" ];
}
];
facts = gen.aspects.graphFacts cnf fleet.config.aspects;
includes = id: facts.includesOf.${id} or [ ];
in
{
includesOf = facts.includesOf;
webserverPullsIn = gen.graph.reachableFrom { edges = includes; } "webserver";
}
{ includesOf = { monitoring = [ ]; ssh = [ ]; webserver = [ "ssh" ]; };
webserverPullsIn = [ "ssh" ]; }

cnf tells gen what each key of an aspect means. Here nixos is a class, so everything under aspects.<name>.nixos is NixOS configuration. A framework can declare more classes, such as one for user environments or one for cloud resources, and other key categories, such as channels.

includes is a relation between aspects, so gen turns it into edges. graphFacts extracts those edges as data, and every gen graph query works on them. The include closure of webserver is one reachableFrom call. Once webserver pulls in ssh, web-1 gets the ssh daemon without anyone writing it into that host’s configuration.

Because an aspect is data, a framework can inspect it before building anything: which classes it writes and which aspects it includes. Delivering each host’s class content to a NixOS system builder, or to any other target, is covered on Aspects and Delivery.

Feature-oriented programming builds a family of related programs from features: units of functionality that a product either includes or leaves out, and that can depend on each other. Each host is one product of that family, assembled from the features it selects. See our reading of Apel et al. (2009). The name aspect comes from aspect-oriented programming, where one unit of code can affect many places in a system (Kiczales et al. 1997).

Next: Stable Identity for Everything.

palette
dark
light
↑↓ select↵ apply esc close

Palettes adapted from Catppuccin (Macchiato) (MIT), Tokyo Night (Apache-2.0), gruvbox (MIT), Catppuccin (Latte) (MIT), Rosé Pine (Dawn) (MIT).