Skip to content

Environments

An environment owns its hosts. It gives them a domain and default settings, and it decides which people may log in. staging inherits from prod what it does not set.

modules/kinds.nix
# fleet > environment > host > user. Groups are data: an environment grants groups, a user is in groups.
{
yen,
lib,
inputs,
...
}:
let
inherit (yen.lib)
same
maybe
opt
is
;
listOf = t: is (v: lib.isList v && lib.all t.check v);
meets = xs: ys: lib.any (x: lib.any (same x) ys) xs;
in
{
yen.kinds.fleet = {
spawns.environment = _: yen.environment;
sinks.out.reduce = yen.lib.reduce.merge;
};
yen.kinds.group = { };
yen.kinds.environment = {
fields = {
domain = lib.types.str;
base = maybe yen.kinds.environment; # settings it does not set come from here
grants = opt (listOf yen.kinds.group) [ ];
settings.timeZone = maybe lib.types.str;
};
spawns.host = s: lib.filterAttrs (_: h: same h.environment s) yen.host;
};
yen.kinds.host = {
fields = {
environment = yen.kinds.environment;
settings.timeZone = maybe lib.types.str;
};
spawns.user = s: lib.filterAttrs (_: u: meets u.groups s.environment.grants) yen.user; # access from data
sinks.nixos.out =
{ host, nixos, ... }:
{
nixosConfigurations.${host.name} = inputs.nixpkgs.lib.nixosSystem { modules = nixos; };
};
};
yen.kinds.user.fields.groups = listOf yen.kinds.group;
yen.fleet.main = { };
yen.flake = { out, ... }: out;
}
modules/data.nix
{ yen, ... }:
{
yen.group = {
admins = { };
devs = { };
};
yen.environment.prod = {
domain = "example.com";
grants = [ yen.group.admins ];
settings.timeZone = "UTC";
};
yen.environment.staging = {
domain = "staging.example.com";
base = yen.environment.prod;
grants = [
yen.group.admins
yen.group.devs
];
};
yen.host.web = {
environment = yen.environment.prod;
};
yen.host.web-next = {
environment = yen.environment.staging;
settings.timeZone = "Europe/Berlin";
};
yen.host.api-next = {
environment = yen.environment.staging;
};
yen.user.ana.groups = [ yen.group.admins ];
yen.user.dev.groups = [ yen.group.devs ];
}

Users are not listed per host. A user lives on a host when one of their groups is in the environment’s grants:

flowchart TD
  n6c4c773b6d["fleet main"]
  n4a308930d1["environment prod"]
  n4019172c9d["host web"]
  n5652c3e60b["user ana"]
  n6a42316005["environment staging"]
  nf2c07df0a5["host api-next"]
  n700800331e["user ana"]
  n83429541a0["user dev"]
  na956585b66["host web-next"]
  naa3774925c["user ana"]
  n473292f4d0["user dev"]
  n6c4c773b6d --> n4a308930d1
  n4a308930d1 --> n4019172c9d
  n4019172c9d --> n5652c3e60b
  n6c4c773b6d --> n6a42316005
  n6a42316005 --> nf2c07df0a5
  nf2c07df0a5 --> n700800331e
  nf2c07df0a5 --> n83429541a0
  n6a42316005 --> na956585b66
  na956585b66 --> naa3774925c
  na956585b66 --> n473292f4d0

yen.lib.cascade takes the first value along host, environment, base environment:

modules/base.nix
# Settings cascade: the host, then its environment, then the environment's base.
{ yen, ... }:
let
setting =
path: host:
yen.lib.cascade path [
host
host.environment
(host.environment.base or null)
];
in
{
yen.aspects.base = {
at = yen.kinds.host;
nixos =
{ host, environment, ... }:
{
networking = {
hostName = host.name;
domain = environment.domain;
};
time.timeZone = setting [ "settings" "timeZone" ] host;
boot.loader.grub.device = "nodev";
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
nixpkgs.hostPlatform = "x86_64-linux";
system.stateVersion = "25.05";
};
};
yen.aspects.time-zones = {
at = yen.kinds.host;
out =
{ host, ... }:
{
timeZones.${host.name} = setting [ "settings" "timeZone" ] host;
}; # goes up to the fleet
};
yen.aspects.account = {
at = yen.kinds.user;
nixos =
{ user, ... }:
{
users.users.${user.name}.isNormalUser = true;
};
};
}
result
{
api-next = "UTC";
web = "UTC";
web-next = "Europe/Berlin";
}
Contribute Community Sponsor