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.
# 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;}{ 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
Settings cascade
Section titled “Settings cascade”yen.lib.cascade takes the first value along host, environment, base environment:
# 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; }; };}{ api-next = "UTC"; web = "UTC"; web-next = "Europe/Berlin";}