Development takes place on Tangled, however, I do maintain a GitHub mirror. Please make PRs to the Tangled repository.
prism #
prism is a library for multi-host NixOS and nix-darwin configurations. Rather
than writing a configuration per host, you describe your machines with tags,
and every module splits (or refracts) its configuration across those tags.
# flake.nix
tags = self: {
graphical = {};
laptop = {
build = true;
parents = [self.graphical];
};
desktop = {
build = true;
parents = [self.graphical];
};
server.build = true;
};
# modules/bluetooth.nix
{
graphical.hardware.bluetooth.enable = true;
desktop.hardware.bluetooth.powerOnBoot = true;
}
laptop and desktop get bluetooth, only desktop powers it on at boot, and
server gets neither.
Features #
Tags with multiple parents #
A tag selects its own sections along with those of every ancestor. Tags can have any number of parents, so machines are described by what they are rather than by which list they appear in.
tags = self: {
graphical = {};
workstation.parents = [self.graphical];
gaming.parents = [self.graphical];
laptop = {
build = true;
parents = [self.workstation];
};
desktop = {
build = true;
parents = [self.workstation self.gaming];
};
deck = {
build = true;
parents = [self.gaming];
};
};
Conditional tags #
A tag with select decides whether it's active on a per-module basis. Here,
chaotic applies to every system with the tag, except in modules
which also configure gaming, where it only applies to gaming systems.
chaotic.select = {module, rootTags, ...}:
!(module ? ${self.gaming.name}) || builtins.elem self.gaming.name rootTags;
Settings per tag #
pkgs, mkSystem, and specialArgs are set once in mkSystems and may be
overridden by any tag. Systems inherit them from their most specific tag which
sets them.
tags = self: {
arm.pkgs = nixpkgs.legacyPackages.aarch64-linux;
desktop = {
build = true;
parents = [self.arm];
};
mac = {
build = true;
pkgs = nixpkgs.legacyPackages.aarch64-darwin;
mkSystem = nix-darwin.lib.darwinSystem;
};
};
Modules per tag #
Tags can bring their own modules and extraModules, so external modules
follow the tag which uses them instead of being listed for every host.
agenix.extraModules = [agenix.nixosModules.default];
Tutorials #
Installation #
Flakes #
- Add
prismto your inputs:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixpkgs-unstable";
prism.url = "git+https://codeberg.org/poacher/prism.git";
};
}
- Create your NixOS configurations with
mkSystems:
{
outputs = {nixpkgs, prism, ...}: {
nixosConfigurations = prism.lib.mkSystems {
mkSystem = nixpkgs.lib.nixosSystem;
pkgs = nixpkgs.legacyPackages.x86_64-linux;
modules = prism.lib.recursivelyImport [./modules];
tags = self: {
# ...
};
};
};
}
Non-Flakes #
- Pin
prismwith your pinner of choice, I'll be usingnpins:
npins add git https://tangled.org/poacher.dev/prism -b main
- Import
prismin your NixOS entry-point:
let
inherit (sources) nixpkgs;
sources = import ./npins;
prism = import sources.prism;
in {
# ...
}
- Create your NixOS configurations using
mkSystems:
let
# ...
in {
nixosConfigurations = prism.mkSystems {
mkSystem = import "${nixpkgs}/nixos/lib/eval-config.nix";
pkgs = import nixpkgs {};
modules = prism.recursivelyImport [./modules];
tags = self: {
# ...
};
};
}
Getting Started #
- Describe your machines. Every tag with
build = truebecomes a system, and theallpreset allows them to share configs:
tags = self: {
inherit (prism.lib.presets) all;
graphical.parents = [self.all];
laptop = {
build = true;
parents = [self.all self.graphical];
};
server = {
build = true;
parents = [self.all];
};
};
- Write a module. Each section is standard NixOS configuration, keyed by the tag it applies to:
# modules/desktop-environment.nix
{
all.services.openssh.enable = true;
laptop.services.power-profiles-daemon.enable = true;
graphical = {pkgs, ...}: {
services.desktopManager.plasma6.enable = true;
environment.systemPackages = [pkgs.firefox];
};
}
Reference #
mkSystems #
mkSystems creates a system for every tag with build = true. It accepts the
following arguments:
tagsa function from the finished tag set (self) to tag definitions.modules ? []prism module files.extraModules ? []non-prism modules imported into every system.mkSystemthe function used to create each system. On NixOS this should benixpkgs.lib.nixosSystem, on Darwin this should benix-darwin.lib.darwinSystem.pkgs ? nullthe nixpkgs instance, set asnixpkgs.pkgs.specialArgs ? {}arguments passed to every module.
Tags #
Each tag is an attribute set which may contain:
parents ? []tags whose sections are also selected, referenced throughself(e.g.self.graphical).buildwhether this tag is a system. Defaults totrueif the tag is a child tag (no other tag lists it as a parent) and doesn't have aselect, otherwisefalse.selecta function deciding, per module, whether this tag is active.mkSystem,pkgs,specialArgsoverrides of themkSystemsdefaults.modules,extraModulesadditions to themkSystemslists.
select #
select receives the following and returns a bool:
rootthe tag being built.rootTagsthe names ofrootand all of its ancestors.tagthe tag being selected.modulethe prism module being selected from.paththe path of that module.tagsevery tag.
Modules #
Modules are attribute sets of tag sections. Each section is standard
configuration passed to mkSystem which only applies to systems where its tag
is selected.
enable ? true is reserved on every module and determines whether or not a
module is evaluated.
{
# Doesn't get evaluated, won't install hyprland
enable = false;
all.programs.hyprland.enable = true;
}
lib.presets #
alla tag to be used as a parent of every system.thesea tag which is active when the module has a section for any of the built tag's tags, other thanall,these, andothers.othersa tag which is active whentheseisn't.
lib.closureOf #
closureOf returns the names of a tag and all of its parents. It can be used
to pass a system's tags to its modules:
laptop = {
build = true;
specialArgs.tags = prism.lib.closureOf self.laptop;
};
lib.childTagsOf #
childTagsOf returns the names of every tag in a tag set that isn't used as
another tag's parent. mkSystems uses this to default build for child tags:
tags = self: {
# ...
specialArgs.childTags = prism.lib.childTagsOf self;
};
lib.recursivelyImport #
recursivelyImport returns every .nix file within a list of paths. Files
starting with _ are ignored.
readOnlyPkgs #
On NixOS, importing nixpkgs.nixosModules.readOnlyPkgs through extraModules is
recommended as it prevents modules from reconfiguring pkgs.
Living Examples #
prism is used in the following configs:
If you would like your config added here then please open an issue or PR.
History #
This project was formerly known as booyah. It has been renamed to prism
after I got peer-pressured.