major refactoring

argument self is now provided via recursion
a naive implementation of host groups is added
This commit is contained in:
do butterflies cry? 2026-02-12 11:04:02 +10:00
parent be916c8674
commit 5c5f3fb65e
3 changed files with 183 additions and 105 deletions

View file

@ -23,8 +23,12 @@
}: let
inherit
(builtins)
attrNames
concatStringsSep
elem
getAttr
isAttrs
isFunction
mapAttrs
pathExists
typeOf
@ -37,7 +41,7 @@
templateNexus = let
inherit
(nt.types)
(nt.naive.terminal)
Terminal
;
@ -47,112 +51,185 @@
Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`.
'');
in {
groups = missing "an list of all valid node group names." "groups";
groups = Terminal {};
overlays = [];
nodes = Terminal {};
};
parseGroups = groups: let
validGroup = g:
isAttrs g
|| throw ''
Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead!
Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`.
'';
delegate = parent: g:
g
|> mapAttrs (name: value:
assert validGroup value;
(delegate g value)
// {
_name = name;
_parent = parent;
});
in
assert validGroup groups;
delegate null groups;
parseNexus = nexus:
if ! isAttrs nexus
then
abort ''
Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead!
Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`.
''
else nt.projectOnto templateNexus nexus;
assert isAttrs nexus
|| abort ''
Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead!
Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`.
''; let
base = nt.projectOnto templateNexus nexus;
in
# XXX: TODO: create a different version of nt.projectOnto that can actually
# XXX: TODO: handle applying a transformation to the result of each datapoint
base
// {
groups = parseGroups base.groups;
};
mkNexus' = root: nexus': let
nexus = parseNexus nexus';
in rec {
nixosConfigurations = mapNodes nexus.nodes (
nodeName: node:
lib.nixosSystem {
system = node.system;
modules = let
host' = root + "/hosts/${nodeName}";
host =
if pathExists host'
then host'
else host' + ".nix";
in
[../nixos-module host] ++ node.extraModules;
# nix passes these to every single module
specialArgs = let
pkgConfig =
{
inherit (node) system;
# XXX: WARNING: TODO: i've stopped caring
# XXX: WARNING: TODO: just figure out a better solution to pkgConfig
config.allowUnfree = true;
overlays = self.overlays ++ nexus.overlays ++ node.overlays;
}
// node.extraPkgConfig;
in
node.specialArgs
// {
pkgs = import nixpkgs pkgConfig;
upkgs = import nixpkgs-unstable pkgConfig;
};
}
parseDecl = outputsBuilder: let
decl = (
if isFunction outputsBuilder
then outputsBuilder final # provide `self`
else
assert (isAttrs outputsBuilder)
|| abort ''
Cerulean declaration must be provided as an attribute set, got "${typeOf outputsBuilder}" instead!
Ensure your declaration is an attribute set or function under your call to `cerulean.mkNexus`.
''; outputsBuilder
);
deploy.nodes = mapNodes nexus.nodes (nodeName: node: let
inherit
(node.deploy)
activationTimeout
autoRollback
confirmTimeout
interactiveSudo
magicRollback
remoteBuild
ssh
sudo
user
;
nixosFor = system: deploy-rs.lib.${system}.activate.nixos;
in {
hostname = ssh.host;
profilesOrder = ["default"]; # profiles priority
profiles.default = {
path = nixosFor node.system nixosConfigurations.${nodeName};
user = user;
sudo = sudo;
interactiveSudo = interactiveSudo;
fastConnection = false;
autoRollback = autoRollback;
magicRollback = magicRollback;
activationTimeout = activationTimeout;
confirmTimeout = confirmTimeout;
remoteBuild = remoteBuild;
sshUser = ssh.user;
sshOpts =
ssh.opts
++ (
if elem "-p" ssh.opts
then []
else ["-p" (toString ssh.port)]
)
++ (
if elem "-A" ssh.opts
then []
else ["-A"]
);
final =
decl
// {
nexus = parseNexus (decl.nexus or {});
};
});
checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib;
};
in {
mkNexus = root: outputs': let
autogen = mkNexus' root (outputs'.nexus or {});
outputs = removeAttrs outputs' ["nexus"];
in
autogen // outputs; # XXX: TODO: replace this with a deep merge
final;
# XXX: TODO: create a function in NixTypes that handles this instead
findImport = path:
if pathExists path
then path
else path + ".nix";
in {
mkNexus = root: outputsBuilder: let
decl = parseDecl outputsBuilder;
inherit
(decl)
nexus
;
customOutputs = removeAttrs decl ["nexus"];
outputs = rec {
nixosConfigurations = mapNodes nexus.nodes (
nodeName: node:
lib.nixosSystem {
system = node.system;
modules = let
host = findImport (root + "/hosts/${nodeName}");
# XXX: TODO: don't use a naive type for this (ie _name property)
# XXX: TODO: i really need NixTypes to be stable and use that instead
groups =
node.groups
|> map (group:
assert group ? _name
|| throw (let
got =
if ! isAttrs group
then toString group
else
group
|> attrNames
|> map (name: "${name} = <${typeOf (getAttr name group)}>;")
|> concatStringsSep " "
|> (x: "{ ${x} }");
in ''
Cerulean Nexus node "${nodeName}" is a member of a nonexistent group.
Got "${got}" of primitive type "${typeOf group}".
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
'');
findImport (root + "/groups/${group._name}"));
in
[../nixos-module host] ++ groups ++ node.extraModules;
# nix passes these to every single module
specialArgs = let
pkgConfig =
{
inherit (node) system;
# XXX: WARNING: TODO: i've stopped caring
# XXX: WARNING: TODO: just figure out a better solution to pkgConfig
config.allowUnfree = true;
overlays = self.overlays ++ nexus.overlays ++ node.overlays;
}
// node.extraPkgConfig;
in
node.specialArgs
// {
pkgs = import nixpkgs pkgConfig;
upkgs = import nixpkgs-unstable pkgConfig;
};
}
);
deploy.nodes = mapNodes nexus.nodes (nodeName: node: let
inherit
(node.deploy)
activationTimeout
autoRollback
confirmTimeout
interactiveSudo
magicRollback
remoteBuild
ssh
sudo
user
;
nixosFor = system: deploy-rs.lib.${system}.activate.nixos;
in {
hostname = ssh.host;
profilesOrder = ["default"]; # profiles priority
profiles.default = {
path = nixosFor node.system nixosConfigurations.${nodeName};
user = user;
sudo = sudo;
interactiveSudo = interactiveSudo;
fastConnection = false;
autoRollback = autoRollback;
magicRollback = magicRollback;
activationTimeout = activationTimeout;
confirmTimeout = confirmTimeout;
remoteBuild = remoteBuild;
sshUser = ssh.user;
sshOpts =
ssh.opts
++ (
if elem "-p" ssh.opts
then []
else ["-p" (toString ssh.port)]
)
++ (
if elem "-A" ssh.opts
then []
else ["-A"]
);
};
});
checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib;
};
in
outputs // customOutputs;
}

View file

@ -22,11 +22,12 @@ in rec {
# abstract node instance that stores all default values
templateNode = name: system: let
inherit
(nt.types)
(nt.naive.terminal)
Terminal
;
in {
system = "x86_64-linux"; # sane default (i hope...)
groups = [];
extraModules = [];
specialArgs = Terminal {};
overlays = [];

View file

@ -31,9 +31,9 @@
...
} @ inputs:
import ./cerulean
<| inputs
// {
inherit (nixpkgs) lib;
inherit (nt) mix;
};
(inputs
// {
inherit (nixpkgs) lib;
inherit (nt) mix;
});
}