Skip to content

From a flake

Most flakes grow the same shape: a mkHost helper, a common.nix, one file per host, and home-manager as a NixOS module. This page moves that shape to yen. The docs check that each host gives the same toplevel drvPath before and after.

before/flake.nix
# A typical flake: a helper builds each host from shared and per-host modules.
{
inputs.nixpkgs.url = "https://nixos.org/channels/nixos-25.05/nixexprs.tar.xz";
inputs.home-manager = {
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
{ nixpkgs, home-manager, ... }:
let
mkHost =
name:
nixpkgs.lib.nixosSystem {
modules = [
./common.nix
./hosts/${name}.nix
home-manager.nixosModules.home-manager
{
networking.hostName = name;
home-manager.useGlobalPkgs = true;
home-manager.users.alice = import ./home.nix;
}
];
};
in
{
nixosConfigurations.web = mkHost "web";
nixosConfigurations.desk = mkHost "desk";
};
}

mkHost knew how to build a host. In yen, the host kind knows it: its nixos sink builds the system. A user’s homeManager sink forwards to nixos on each host the user lives under.

modules/fleet.nix
# Hosts build NixOS. Users live under their hosts; a user's home-manager content goes into each host.
{
yen,
lib,
inputs,
...
}:
{
yen.kinds.fleet = {
spawns.host = _: yen.host;
sinks.out.reduce = yen.lib.reduce.merge;
};
yen.kinds.host = {
spawns.user = s: lib.filterAttrs (_: u: lib.any (yen.lib.same s) u.hosts) yen.user;
sinks.nixos.out =
{ host, nixos, ... }:
{
nixosConfigurations.${host.name} = inputs.nixpkgs.lib.nixosSystem { modules = nixos; };
};
};
yen.kinds.user = {
fields.hosts = yen.lib.is lib.isList;
sinks.homeManager.nixos =
{ user, homeManager, ... }:
{
home-manager.users.${user.name}.imports = homeManager;
};
};
yen.fleet.main = { };
yen.flake = { out, ... }: out;
}

common.nix was in every host’s list. As an aspect it is active at every host. home-manager is active only where a user lives:

modules/features/common.nix
# The old common.nix and the mkHost helper, as aspects at every host.
{ yen, inputs, ... }:
{
yen.aspects.common = {
at = yen.kinds.host;
nixos =
{ host, pkgs, ... }:
{
networking.hostName = host.name;
boot.loader.systemd-boot.enable = true;
time.timeZone = "UTC";
services.openssh.enable = true;
environment.systemPackages = [ pkgs.git ];
system.stateVersion = "25.05";
};
};
yen.aspects.home-manager = {
at = yen.lib.has yen.kinds.user; # only hosts with users
nixos = {
imports = [ inputs.home-manager.nixosModules.home-manager ];
home-manager.useGlobalPkgs = true;
};
};
}

A host file declares the instance and the parts only that host has. Features that only one host uses are aspects with that host in at:

modules/hosts/web.nix
{ yen, ... }:
{
yen.host.web = { };
yen.aspects.web-hardware = {
at = yen.host.web;
nixos = {
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
nixpkgs.hostPlatform = "x86_64-linux";
};
};
}

home-manager.users.alice was written in the flake for every host. Now alice is data: she says which hosts she uses, and her aspect gives her account and her home:

modules/alice.nix
# A user: an account on each of her hosts, and her home.
{ yen, ... }:
{
yen.user.alice.hosts = [
yen.host.web
yen.host.desk
];
yen.aspects.alice = {
at = yen.user.alice;
nixos =
{ user, ... }:
{
users.users.${user.name} = {
isNormalUser = true;
extraGroups = [ "wheel" ];
};
};
homeManager = {
programs.git = {
enable = true;
userName = "Alice";
};
home.stateVersion = "25.05";
};
};
}
Contribute Community Sponsor