Skip to content

Getting started

This page builds one NixOS host with yen. You write four small files. At the end you have a nixosConfigurations.laptop that nixos-rebuild can use.

  1. Get the template. It has a flake.nix and a modules/ directory.

    Terminal window
    nix flake init -t github:vic/yen#getting-started
  2. Read flake.nix. The yen input is a normal flake input. import-tree loads every file in modules/.

    flake.nix
    {
    description = "yen: one NixOS host";
    outputs =
    inputs:
    inputs.yen.lib.mkFlake {
    imports = [ (inputs.import-tree ./modules) ];
    _module.args = {
    inherit inputs;
    inherit (inputs.nixpkgs) lib;
    };
    };
    inputs = {
    yen.url = "github:vic/yen";
    import-tree.url = "github:denful/import-tree";
    nixpkgs.url = "https://nixos.org/channels/nixos-25.05/nixexprs.tar.xz";
    };
    }
  3. Build the host:

    Terminal window
    nix build .#nixosConfigurations.laptop.config.system.build.toplevel

yen has no built-in kinds. You say what a fleet is. The root kind is the one that nothing spawns. Here the root fleet spawns every host, and its out sink is the flake.

modules/fleet.nix
# The root of the tree. Nothing spawns it. Its `out` sink is the flake.
{ yen, ... }:
{
yen.kinds.fleet = {
spawns.host = _: yen.host; # every host lives under the fleet
sinks.out.reduce = yen.lib.reduce.merge;
};
yen.fleet.main = { };
yen.flake = { out, ... }: out;
}

A kind has fields (the data of each instance) and sinks (where behavior goes). The nixos sink collects NixOS modules. Its out key gives the built system to the flake.

modules/host.nix
# A host has a system. Its `nixos` sink collects NixOS modules and builds the host.
{
yen,
lib,
inputs,
...
}:
{
yen.kinds.host = {
fields.system = lib.types.str;
sinks.nixos.out =
{ host, nixos, ... }:
{
nixosConfigurations.${host.name} = inputs.nixpkgs.lib.nixosSystem {
inherit (host) system;
modules = nixos;
};
};
};
yen.host.laptop.system = "x86_64-linux";
}

An aspect is active where its at holds. Each key of an aspect gives one element to the sink of that name. base is active at every host. motd is active at laptop only.

modules/aspects.nix
# Behavior: active at every host, gives one NixOS module to the `nixos` sink.
{ yen, ... }:
{
yen.aspects.base = {
at = yen.kinds.host;
nixos =
{ host, ... }:
{
networking.hostName = host.name;
boot.loader.grub.device = "nodev";
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
system.stateVersion = "25.05";
};
};
yen.aspects.motd = {
at = yen.host.laptop;
nixos.environment.etc.motd.text = "managed by yen";
};
}

yen answers queries on the fleet. This is the scope tree of this example, generated from r.graph:

flowchart TD
  n6c4c773b6d["fleet main"]
  ne01d911511["host laptop"]
  n6c4c773b6d --> ne01d911511

And r.active: which aspect is active at which scope.

result
{
base = {
"fleet:main" = false;
"fleet:main/host:laptop" = true;
};
motd = {
"fleet:main" = false;
"fleet:main/host:laptop" = true;
};
}
Contribute Community Sponsor