includes, provides and meta
This page follows the structure of drupol/infra, one of the largest public Den configurations: ten hosts, one user on most of them, and about a hundred feature files. The same shapes appear in most Den configurations on GitHub. Each section shows the yen files first. A table at the end maps each Den form to its yen form.
The fleet
Section titled “The fleet”First the schema. Den keeps data in a free-form meta and in aspect arguments. yen declares them as typed
fields of a kind.
# Den's schema, declared. A user's `meta` attrs become typed fields; `facter ./facter.json` becomes a host field.{ yen, inputs, ...}:let inherit (yen.lib) opt; strs = yen.lib.listOf yen.lib.str;in{ yen.kinds.fleet = { spawns.host = _: yen.host; sinks.out.reduce = yen.lib.reduce.merge; }; yen.kinds.host = { fields = { system = opt yen.lib.str "x86_64-linux"; shares = opt strs [ ]; # (1) parameters of a parametric aspect }; spawns.user = yen.lib.pointing "hosts" yen.user; sinks.nixos.out = { host, nixos, ... }: { nixosConfigurations.${host.name} = inputs.nixpkgs.lib.nixosSystem { modules = nixos; }; }; }; yen.kinds.user.fields = { hosts = yen.lib.listOf yen.kinds.host; fullname = yen.lib.str; # (2) authorizedKeys = opt strs [ ]; };
yen.fleet.main = { }; yen.flake = { out, ... }: out;}(1) is the shares field of a host: a list of strings, empty by default. A parametric Den aspect, such as
mount-cifs "//nas/media", takes its argument from this field. (2) is the fullname field of a user. A
user without it is an error.
One feature in many files
Section titled “One feature in many files”Each file under modules/base/ writes part of the base aspect. yen reads all of them.
A NixOS module from each file reaches every host, because of the at at (1).
# One feature, many files: each file of modules/base/ adds to the same `base` aspect.{ yen, ... }:{ yen.aspects.base = { at = yen.kinds.host; # (1) den.default.includes: every host nixos.nix.settings.experimental-features = [ "nix-command" "flakes" ]; };}{ yen.aspects.base.nixos.i18n.defaultLocale = "en_GB.UTF-8";}{ yen.aspects.base.nixos = { host, ... }: { networking.hostName = host.name; nixpkgs.hostPlatform = host.system; system.stateVersion = "25.05"; };}Hosts pick features
Section titled “Hosts pick features”In Den, a host file lists the features it includes. In yen a feature has no at of its own, and each host file
adds the host to the at of the features it wants. at from several files is a union, so the list stays in the
host file, as in Den. The first file declares the features. The tabs are two host files.
# Features without `at`: each host file says which ones it has.{ lib, ... }:{ yen.aspects.desktop.nixos.services.xserver.desktopManager.gnome.enable = true; yen.aspects.openssh.nixos.services.openssh.enable = true; # (den.aspects.mount-cifs "//nas/media"): the parameters are the host's `shares` field yen.aspects.shares = { # (1) at = s: (s.shares or [ ]) != [ ]; nixos = { host, ... }: { fileSystems = lib.listToAttrs ( map ( share: lib.nameValuePair "/mnt/${baseNameOf share}" { device = share; fsType = "cifs"; options = [ "noauto" ]; } ) host.shares ); }; };}# Den: provides.to-users.includes = with den.aspects; [ desktop openssh … ]. yen: the host joins each feature's `at`.{ yen, ... }:let host = yen.host.x280;in{ yen.host.x280.shares = [ "//nas/media" ]; yen.aspects.desktop.at = [ host ]; yen.aspects.openssh.at = [ host ]; # (1) yen.aspects.x280 = { at = host; nixos = { boot.loader.systemd-boot.enable = true; fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; }; }; }; # den.aspects.pol.provides.x280: pol's content, only on this host yen.aspects.pol-on-x280 = { at = yen.lib.all [ # (2) yen.user.pol (yen.lib.within host) ]; nixos.users.users.pol.extraGroups = [ "dialout" ]; };}{ yen, ... }:{ yen.host.rpi4.system = "aarch64-linux"; yen.aspects.openssh.at = [ yen.host.rpi4 ]; # `at` from two files is a union yen.aspects.rpi4 = { at = yen.host.rpi4; nixos = { boot.loader.generic-extlinux-compatible.enable = true; fileSystems."/" = { device = "/dev/disk/by-label/NIXOS_SD"; fsType = "ext4"; }; }; };}In features.nix, (1) is the aspect shares. Its at is true where the shares field is not empty, so it
needs no host list. desktop and openssh have no at here.
In hosts/x280.nix, (1) adds x280 to the at of openssh. hosts/rpi4.nix does the same for rpi4, and
the two lists join. (2) is the provides of Den: the aspect is active at the user pol only where pol is
within the host x280.
User meta is fields
Section titled “User meta is fields”Den users often keep a free-form meta attribute set and read it back with config.meta. In yen these are
fields of the user kind, declared in fleet.nix above. A missing fullname is an error at the user, and every
file can read the value.
# Den: den.aspects.pol = { config, ... }: { meta.fullname = …; user.description = config.meta.fullname; }.# yen: meta is fields of the instance. Any file reads them as `yen.user.pol.fullname`, or as the `user` arg.{ yen, ... }:{ yen.user.pol = { hosts = [ yen.host.x280 yen.host.rpi4 ]; fullname = "Pol"; authorizedKeys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPol" ]; }; # define-user + primary-user, for every user yen.aspects.account = { at = yen.kinds.user; nixos = { user, ... }: { users.users.${user.name} = { isNormalUser = true; description = user.fullname; extraGroups = [ "wheel" ]; openssh.authorizedKeys.keys = user.authorizedKeys; }; }; };}From Den to yen
Section titled “From Den to yen”Each row is a Den form and the yen form of it.
| Den | yen |
|---|---|
den.aspects.base written in 26 files |
the same: yen.aspects.base in many files; nothing is lost, see One feature in many files |
den.default.includes = [ base ] |
at = yen.kinds.host on the aspect |
a host’s includes or provides.to-users.includes = [ desktop openssh … ] |
the host file adds the host to each feature’s at |
(den.aspects.facter ./facter.json), (mount-cifs "//nas/media") |
a host field, and one aspect whose at tests the field |
den.aspects.pol.provides.x280, host-aspects |
an aspect at all [ yen.user.pol (within yen.host.x280) ]; see Predicates |
meta = { fullname; email; authorizedKeys; }, read with config.meta |
typed user fields, read as user.fullname or yen.user.pol.fullname |
den.aspects.tools.provides.nix-trusted-user |
an aspect with its own name |
hasAspect |
an aspect in a predicate: at = all [ yen.kinds.user (within yen.aspects.gnome) ] |