Skip to content

Roles from data

Host files that list roles drift: someone adds a GPU and forgets the line. Put the facts on the host instead, and let each role test them.

modules/kinds.nix
# Machines carry facts. No host file names its roles: the facts decide.
{ yen, lib, ... }:
let
inherit (yen.lib) opt is;
in
{
yen.kinds.fleet = {
spawns.host = _: yen.host;
sinks.out.reduce = yen.lib.reduce.merge;
};
yen.kinds.host.fields = {
form = lib.types.enum [
"server"
"laptop"
"desktop"
];
gpu = opt (lib.types.enum [
"none"
"nvidia"
"amd"
]) "none";
labels = opt (is (v: lib.isList v && lib.all lib.isString v)) [ ];
};
yen.fleet.main = { };
yen.host.rack-1 = {
form = "server";
labels = [ "ci-runner" ];
};
yen.host.rack-2.form = "server";
yen.host.thinkpad.form = "laptop";
yen.host.tower = {
form = "desktop";
gpu = "nvidia";
};
yen.flake = { out, ... }: out;
}
modules/roles.nix
# A role is an aspect whose `at` is a fact test. Roles build on roles.
{ yen, lib, ... }:
let
inherit (yen.lib) all not;
in
{
yen.aspects = {
server.at = s: (s.form or null) == "server";
workstation.at =
s:
lib.elem (s.form or null) [
"laptop"
"desktop"
];
laptop.at = s: (s.form or null) == "laptop";
nvidia.at = s: (s.gpu or null) == "nvidia";
ci-runner.at = s: lib.elem "ci-runner" (s.labels or [ ]);
gaming.at = all [
yen.aspects.workstation
yen.aspects.nvidia
(not yen.aspects.laptop)
];
backups.at = [
yen.aspects.server
yen.aspects.workstation
];
};
}

Roles combine like any predicate: gaming is a workstation with an nvidia GPU that is not a laptop. A role gives content with the usual keys (nixos, homeManager, …), and other aspects can use it in at.

yen.lib.holds asks a predicate at a scope. One aspect turns the active roles into tags:

modules/tags.nix
# Tags from active roles: `yen.lib.holds` asks an aspect at a scope. A deploy tool can read them.
{ yen, lib, ... }:
{
yen.aspects.tags = {
at = yen.kinds.host;
out =
{ host, scope, ... }:
{
tags.${host.name} = lib.filter (r: yen.lib.holds yen.aspects.${r} scope) [
"server"
"workstation"
"laptop"
"nvidia"
"ci-runner"
"gaming"
"backups"
];
};
};
}
result
{
rack-1 = [
"server"
"ci-runner"
"backups"
];
rack-2 = [
"server"
"backups"
];
thinkpad = [
"workstation"
"laptop"
"backups"
];
tower = [
"workstation"
"nvidia"
"gaming"
"backups"
];
}
Contribute Community Sponsor