Skip to content

NixOS batteries

yen.lib.batteries.nixos holds the NixOS rules that most fleets write. NixOS is the domain of these batteries: they set NixOS options. They still name no yen kind, sink or aspect. You write yen.kinds.host, the sinks and every aspect, in your own words. This is a fleet of hosts that hold users:

modules/fleet.nix
# Your words: hosts build NixOS and hold users. The batteries fill values; every key here is yours.
{
yen,
inputs,
...
}:
let
nixos = yen.lib.batteries.nixos;
in
{
yen.kinds.fleet = {
spawns.host = _: yen.host;
sinks.out.reduce = yen.lib.reduce.merge;
};
yen.kinds.host = {
spawns.user = yen.lib.pointing "hosts" yen.user;
sinks.nixos.out = nixos.system {
# (1)
from = yen.kinds.host.sinks.nixos;
inherit (inputs) nixpkgs;
};
# package names from any feature or user; one module per host allows them
sinks.unfree.nixos = nixos.unfree { from = yen.kinds.host.sinks.unfree; }; # (2)
sinks.insecure.nixos = nixos.insecure { from = yen.kinds.host.sinks.insecure; }; # (3)
};
yen.kinds.user.fields = {
hosts = yen.lib.listOf yen.kinds.host;
shell = yen.lib.opt (yen.lib.enum [
"bash"
"fish"
"zsh"
]) "bash";
};
yen.kinds.host.fields.autologin = yen.lib.maybe yen.kinds.user;
yen.fleet.main = { };
yen.flake = { out, ... }: out;
}
  • (1) is system, the out key of the sink that collects NixOS modules. It builds nixosConfigurations.<host>.
  • (2) and (3) are the NixOS keys of two sinks of package names. One module per host allows or marks them. Any aspect can give names to these sinks.

These are the data of the fleet and the aspect that gives the NixOS base to the hosts:

modules/machines.nix
{ yen, ... }:
{
yen.host.desk = {
addr = "10.0.0.2";
sshKey = "ssh-ed25519 AAAAdesk";
autologin = yen.user.ana;
};
yen.host.nas = {
addr = "10.0.0.1";
sshKey = "ssh-ed25519 AAAAnas";
};
yen.user.ana = {
hosts = [ yen.host.desk ];
shell = "fish";
};
yen.user.bob.hosts = [ yen.host.desk ];
yen.aspects.desk = {
at = [
yen.host.desk
yen.host.nas
];
nixos = {
boot.loader.grub.device = "nodev";
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
nixpkgs.hostPlatform = "x86_64-linux";
system.stateVersion = "25.05";
};
};
yen.aspects.gaming = {
at = yen.host.desk;
unfree = [
"steam"
"steam-unwrapped"
];
nixos.programs.steam.enable = true;
};
yen.aspects.bob-tools = {
at = yen.user.bob;
unfree = [ "vscode" ];
insecure = [ "olm-3.2.16" ];
};
}

The aspects that set the host name, the users and their shells:

modules/aspects.nix
# Den's hostname, define-user and primary-user, as values of aspects you name.
{ yen, ... }:
let
nixos = yen.lib.batteries.nixos;
in
{
yen.aspects.hostname = {
at = yen.kinds.host;
nixos = nixos.hostname; # (1)
};
yen.aspects.accounts = {
at = yen.kinds.user; # a user has no `nixos` sink: its account goes into each of its hosts
nixos = nixos.define-user; # (2)
};
yen.aspects.admin = {
at = yen.user.ana;
nixos = nixos.primary-user { }; # (3)
};
# Den's user-shell and tty-autologin: the battery reads the field you name
yen.aspects.shells = {
at = yen.kinds.user;
nixos = nixos.user-shell { shell = u: u.shell; }; # (4)
};
yen.aspects.autologin = {
at = yen.kinds.host;
nixos = nixos.autologin { user = h: h.autologin; }; # (5)
};
}

Each battery is the value of the nixos key of an aspect. (1) sets the host name from the name of the scope. (2) defines a user’s account. The kind user has no nixos sink, so the account goes into each of its hosts. (3) makes ana the primary user. (4) reads the shell field that you name. At (5), user reads a ref or a name.

Ports, peers and builders:

modules/network.nix
# Ports, peers and builders: sinks you name, NixOS values from batteries.
{ yen, lib, ... }:
let
nixos = yen.lib.batteries.nixos;
str = yen.lib.str;
in
{
yen.kinds.host = {
fields = {
addr = str;
sshKey = str;
};
sinks.ports.nixos = nixos.firewall { from = yen.kinds.host.sinks.ports; }; # (1)
};
# every host gives a peer record and a builder record to the fleet; each host reads the others'
yen.kinds.fleet.sinks = {
peers.reduce = yen.lib.reduce.elements;
builders.reduce = yen.lib.reduce.elements;
};
yen.aspects.peer = {
at = yen.kinds.host;
peers =
{ host, ... }:
{
inherit (host) name addr;
key = host.sshKey;
};
builders =
{ host, ... }:
{
hostName = host.addr;
system = "x86_64-linux";
maxJobs = 4;
};
nixos = nixos.hosts { from = yen.kinds.fleet.sinks.peers; }; # (2)
};
yen.aspects.builds = {
at = yen.kinds.host;
nixos = nixos.remote-builders { from = yen.kinds.fleet.sinks.builders; }; # (3)
};
yen.aspects.ssh = {
at = yen.kinds.host;
ports = 22;
nixos.services.openssh.enable = true;
};
yen.aspects.dns = {
at = yen.host.nas;
ports = {
tcp = [ 53 ];
udp = [ 53 ];
};
};
}

(1) opens the ports that aspects give: 22, or { tcp; udp; }. At (2) and (3), every host gives a record to the fleet, and each host reads the records of the other machines: /etc/hosts and known_hosts, and nix.buildMachines.

Paths that survive a reboot, and hardware:

modules/storage.nix
# Paths that survive a reboot, from any feature (impermanence), and hardware from a facter report where one exists.
{ yen, inputs, ... }:
let
nixos = yen.lib.batteries.nixos;
in
{
yen.kinds.host.sinks.persist.nixos = nixos.persist {
# (1)
from = yen.kinds.host.sinks.persist;
hideMounts = true;
};
yen.aspects.impermanence = {
at = yen.kinds.host;
nixos.imports = [ inputs.impermanence.nixosModules.impermanence ];
};
yen.aspects.ssh-state = {
at = yen.kinds.host;
persist.files = [ "/etc/ssh/ssh_host_ed25519_key" ];
};
yen.aspects.ana-state = {
at = yen.user.ana; # from a user: up to each of her hosts
persist = "/home/ana/.local/share";
};
# facter: only hosts with a report under modules/<host>/
yen.aspects.facter = {
at = s: builtins.pathExists (./. + "/${s.name}/facter.json");
nixos = nixos.facter {
# (2)
report = h: ./. + "/${h.name}/facter.json";
module = inputs.nixos-facter-modules.nixosModules.facter;
};
};
}

(1) collects paths from any aspect, for impermanence. A path from a user goes up to each of her hosts. (2) adds a facter module only at hosts that have a report.

Battery Value of Den
system { from; nixpkgs; } the out key of the sink that collects NixOS modules the den.hosts schema
hostname a NixOS key of an aspect den.batteries.hostname
define-user a NixOS key of an aspect den.batteries.define-user
primary-user { user ? ; } a NixOS key; user reads a ref or a name, by default the scope den.batteries.primary-user
unfree { from; } the NixOS key of a sink of package names den.batteries.unfree
insecure { from; } the NixOS key of a sink of package names den.batteries.insecure
user-shell { shell; } a NixOS key; shell reads the scope den.batteries.user-shell
autologin { user; } a NixOS key; user reads a ref or a name den.batteries.tty-autologin
firewall { from; } the NixOS key of a sink of ports (22 or { tcp; udp; }) quirks firewall
hosts { from; } a NixOS key; /etc/hosts and known_hosts of the other machines (mesh) sini hostsfile
remote-builders { from; systemOf; } a NixOS key; nix.buildMachines of the other machines (mesh) sini remote-build-client
persist { from; root; hideMounts; } the NixOS key of a sink of paths (impermanence) quirks persist
facter { report; module; } a NixOS key, at hosts that have a report den.aspects.facter

mesh is a generic battery. Each battery is a few lines. The pages Every Den battery and Unfree packages from many files write the same rules by hand. When a battery does not fit your fleet, copy those lines and change them: see Write your own.

ContributeCommunitySponsor