node inheritance + group parsing
This commit is contained in:
parent
d891a92357
commit
f819933c8d
12 changed files with 422 additions and 253 deletions
|
|
@ -18,15 +18,14 @@
|
||||||
} @ args: let
|
} @ args: let
|
||||||
inherit (nt) findImport;
|
inherit (nt) findImport;
|
||||||
in
|
in
|
||||||
mix.newMixture args (mixture: let
|
mix.newMixture args (mixture: {
|
||||||
inherit (mixture) mapNodes;
|
|
||||||
in {
|
|
||||||
includes = {
|
includes = {
|
||||||
private = [
|
private = [
|
||||||
./lib/nodes.nix
|
./lib/nodes.nix
|
||||||
];
|
];
|
||||||
public = [
|
public = [
|
||||||
./flake
|
./flake
|
||||||
|
./lib.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
No option has been declared for this flake output attribute, so its definitions can't be merged automatically.
|
No option has been declared for this flake output attribute, so its definitions can't be merged automatically.
|
||||||
Possible solutions:
|
Possible solutions:
|
||||||
- Load a module that defines this flake output attribute
|
- Load a module that defines this flake output attribute
|
||||||
Many modules are listed at https://flake.parts
|
|
||||||
- Declare an option for this flake output attribute
|
- Declare an option for this flake output attribute
|
||||||
- Make sure the output attribute is spelled correctly
|
- Make sure the output attribute is spelled correctly
|
||||||
- Define the value only once, with a single definition in a single module
|
- Define the value only once, with a single definition in a single module
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
{
|
{
|
||||||
|
_snow,
|
||||||
lib,
|
lib,
|
||||||
specialArgs,
|
specialArgs,
|
||||||
...
|
...
|
||||||
|
|
@ -25,38 +26,20 @@
|
||||||
in
|
in
|
||||||
mkOption {
|
mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Cerulean node declarations.
|
Snowflake node declarations.
|
||||||
'';
|
'';
|
||||||
type = types.submoduleWith {
|
type = types.submoduleWith {
|
||||||
inherit specialArgs;
|
inherit specialArgs;
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
{
|
./nodes.nix
|
||||||
imports = [./shared.nix];
|
|
||||||
|
|
||||||
options = {
|
|
||||||
groups = mkOption {
|
|
||||||
type = types.attrs;
|
|
||||||
default = {};
|
|
||||||
example = lib.literalExpression "{ servers = { staging = {}; production = {}; }; }";
|
|
||||||
description = ''
|
|
||||||
Hierarchical groups that nodes can be a member of.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
nodes = mkOption {
|
|
||||||
type = types.attrsOf (types.submoduleWith {
|
|
||||||
inherit specialArgs;
|
|
||||||
modules = [(import ./submodule.nix)];
|
|
||||||
});
|
|
||||||
# example = { ... }; # TODO
|
|
||||||
description = ''
|
|
||||||
Node (host systems) declarations.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
nodes = {
|
||||||
|
base = _snow.inputs.nixpkgs;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
73
nix/snow/flake/nodes/groups.nix
Normal file
73
nix/snow/flake/nodes/groups.nix
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# # Copyright 2025-2026 _cry64 (Emile Clark-Boman)
|
||||||
|
# #
|
||||||
|
# # Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# # you may not use this file except in compliance with the License.
|
||||||
|
# # You may obtain a copy of the License at
|
||||||
|
# #
|
||||||
|
# # http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
# #
|
||||||
|
# # Unless required by applicable law or agreed to in writing, software
|
||||||
|
# # distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# # See the License for the specific language governing permissions and
|
||||||
|
# # limitations under the License.
|
||||||
|
{nt, ...}: let
|
||||||
|
inherit
|
||||||
|
(builtins)
|
||||||
|
concatLists
|
||||||
|
elem
|
||||||
|
filter
|
||||||
|
isAttrs
|
||||||
|
mapAttrs
|
||||||
|
pathExists
|
||||||
|
typeOf
|
||||||
|
;
|
||||||
|
|
||||||
|
rootGroupName = "all";
|
||||||
|
in {
|
||||||
|
parseGroupsDecl = groups: let
|
||||||
|
validGroup = g:
|
||||||
|
isAttrs g
|
||||||
|
|| throw ''
|
||||||
|
Snow node groups must be provided as attribute sets, got "${typeOf g}" instead!
|
||||||
|
Ensure all the group definitions are attribute sets under your call to `snow.flake`.
|
||||||
|
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
|
||||||
|
'';
|
||||||
|
delegate = parent: gName: g: let
|
||||||
|
result =
|
||||||
|
(g
|
||||||
|
// {
|
||||||
|
_name = gName;
|
||||||
|
_parent = parent;
|
||||||
|
})
|
||||||
|
|> mapAttrs (name: value:
|
||||||
|
if elem name ["_name" "_parent"]
|
||||||
|
# ignore metadata fields
|
||||||
|
then value
|
||||||
|
else assert validGroup value; (delegate result name value));
|
||||||
|
in
|
||||||
|
result;
|
||||||
|
in
|
||||||
|
assert validGroup groups;
|
||||||
|
delegate null rootGroupName groups;
|
||||||
|
|
||||||
|
getGroupModules = root: groups:
|
||||||
|
# ensure root group is always added
|
||||||
|
groups
|
||||||
|
# add all inherited groups via _parent
|
||||||
|
|> map (let
|
||||||
|
delegate = g:
|
||||||
|
if g._parent == null
|
||||||
|
then [g]
|
||||||
|
else [g] ++ delegate (g._parent);
|
||||||
|
in
|
||||||
|
delegate)
|
||||||
|
# flatten recursion result
|
||||||
|
|> concatLists
|
||||||
|
# find import location
|
||||||
|
|> map (group: nt.findImport /${root}/groups/${group._name})
|
||||||
|
# filter by uniqueness
|
||||||
|
|> nt.prim.unique
|
||||||
|
# ignore missing groups
|
||||||
|
|> filter pathExists;
|
||||||
|
}
|
||||||
|
|
@ -14,16 +14,18 @@
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
systems,
|
systems,
|
||||||
|
config,
|
||||||
|
nodesConfig,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
imports = [./shared.nix];
|
|
||||||
|
|
||||||
options = let
|
options = let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
mkOption
|
mkOption
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
|
||||||
|
flakeRef = types.either types.str types.path;
|
||||||
in {
|
in {
|
||||||
enabled = lib.mkOption {
|
enabled = lib.mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
|
|
@ -43,6 +45,65 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
base = lib.mkOption {
|
||||||
|
# In newer Nix versions, particularly with lazy trees, outPath of
|
||||||
|
# flakes becomes a Nix-language path object. We deliberately allow this
|
||||||
|
# to gracefully come through the interface in discussion with @roberth.
|
||||||
|
#
|
||||||
|
# See: https://github.com/NixOS/nixpkgs/pull/278522#discussion_r1460292639
|
||||||
|
type = types.nullOr flakeRef;
|
||||||
|
|
||||||
|
default = nodesConfig.base;
|
||||||
|
defaultText = "nodes.base";
|
||||||
|
|
||||||
|
example = lib.literalExpression "inputs.nixpkgs";
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
The path to the nixpkgs source used to build a system. A `base` package set
|
||||||
|
is required to be set, and can be specified via either:
|
||||||
|
1. `options.nodes.base` (default `base` used for all systems)
|
||||||
|
2. `options.nodes.nodes.<name>.base` (takes prescedence over `options.nodes.base`)
|
||||||
|
|
||||||
|
This can also be optionally set if the NixOS system is not built with a flake but still uses
|
||||||
|
pinned sources: set this to the store path for the nixpkgs sources used to build the system,
|
||||||
|
as may be obtained by `fetchTarball`, for example.
|
||||||
|
|
||||||
|
Note: the name of the store path must be "source" due to
|
||||||
|
<https://github.com/NixOS/nix/issues/7075>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
homeManager = mkOption {
|
||||||
|
type = types.nullOr flakeRef;
|
||||||
|
default = nodesConfig.homeManager;
|
||||||
|
defaultText = "nodes.homeManager";
|
||||||
|
example = lib.literalExpression "inputs.home-manager";
|
||||||
|
description = ''
|
||||||
|
The path to the home-manager source. A `homeManager` flake reference
|
||||||
|
is required to be set for `homes/` to be evaluated, and can be specified via either:
|
||||||
|
1. `options.nodes.homeManager` (default `homManager` used for all systems)
|
||||||
|
2. `options.nodes.nodes.<name>.homeManager` (takes prescedence over `options.nodes.homeManager`)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = mkOption {
|
||||||
|
type = types.listOf types.raw;
|
||||||
|
default = [];
|
||||||
|
example = lib.literalExpression "[ { environment.systemPackages = [ pkgs.git ]; } ]";
|
||||||
|
description = ''
|
||||||
|
Shared modules to import; equivalent to the NixOS module system's `extraModules`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
args = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
example = lib.literalExpression "{ inherit inputs; }";
|
||||||
|
description = ''
|
||||||
|
Shared args to provided for each node; equivalent to the NixOS module system's `specialArgs`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
groups = mkOption {
|
groups = mkOption {
|
||||||
# TODO: write a custom group type that validates better than types.attrs lol
|
# TODO: write a custom group type that validates better than types.attrs lol
|
||||||
type = types.functionTo (types.listOf types.attrs);
|
type = types.functionTo (types.listOf types.attrs);
|
||||||
|
|
@ -51,6 +112,9 @@
|
||||||
description = ''
|
description = ''
|
||||||
A function from the `groups` hierarchy to a list of groups this node inherits from.
|
A function from the `groups` hierarchy to a list of groups this node inherits from.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
apply = groupsFn:
|
||||||
|
groupsFn nodesConfig.groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
deploy = {
|
deploy = {
|
||||||
|
|
@ -91,7 +155,7 @@
|
||||||
example = false;
|
example = false;
|
||||||
description = ''
|
description = ''
|
||||||
Whether to enable interactive sudo (password based sudo).
|
Whether to enable interactive sudo (password based sudo).
|
||||||
NOT RECOMMENDED. Use one of Cerulean's recommended auth methods instead.
|
NOT RECOMMENDED. Use one of Snowflake's recommended auth methods instead.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -164,7 +228,7 @@
|
||||||
|
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "cerubld";
|
default = "snowbld";
|
||||||
example = "custom-user";
|
example = "custom-user";
|
||||||
description = ''
|
description = ''
|
||||||
The user to connect to over ssh during deployment.
|
The user to connect to over ssh during deployment.
|
||||||
|
|
@ -183,7 +247,7 @@
|
||||||
publicKeys = mkOption {
|
publicKeys = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [];
|
default = [];
|
||||||
example = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIeyZuUUmyUYrYaEJwEMvcXqZFYm1NaZab8klOyK6Imr me@puter"];
|
example = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIeyZuUUmyUYrYaEJwEMvcXqZFYm1NaZab8klOyK6Imr me@myputer"];
|
||||||
description = ''
|
description = ''
|
||||||
SSH public keys that will be authorized to the deployment user.
|
SSH public keys that will be authorized to the deployment user.
|
||||||
This key is intended solely for deployment, allowing for fine-grained permission control.
|
This key is intended solely for deployment, allowing for fine-grained permission control.
|
||||||
|
|
@ -201,4 +265,33 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config = let
|
||||||
|
throwGotNull = name:
|
||||||
|
throw ''
|
||||||
|
[snow] `nodes.<name>.${name}` must be set for all nodes! (got: <null>)
|
||||||
|
'';
|
||||||
|
givenSystem =
|
||||||
|
(config.system != null)
|
||||||
|
|| throwGotNull "system";
|
||||||
|
|
||||||
|
givenBase =
|
||||||
|
(config.base != null)
|
||||||
|
|| throwGotNull "base";
|
||||||
|
|
||||||
|
givenHomeManager =
|
||||||
|
(config.homeManager != null)
|
||||||
|
|| throwGotNull "homeManager";
|
||||||
|
|
||||||
|
givenDeployHost =
|
||||||
|
(config.deploy.ssh.host != null)
|
||||||
|
|| throwGotNull "deploy.ssh.host";
|
||||||
|
in
|
||||||
|
assert givenSystem
|
||||||
|
&& givenBase
|
||||||
|
&& givenHomeManager
|
||||||
|
&& givenDeployHost; {
|
||||||
|
# extend these from the nodes configuration
|
||||||
|
inherit (nodesConfig) modules args;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,13 @@
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
{lib, ...}: let
|
{
|
||||||
|
_snow,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
specialArgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
mkOption
|
mkOption
|
||||||
|
|
@ -19,6 +25,8 @@
|
||||||
;
|
;
|
||||||
|
|
||||||
flakeRef = types.either types.str types.path;
|
flakeRef = types.either types.str types.path;
|
||||||
|
|
||||||
|
groupLibs = import ./groups.nix {inherit (_snow.inputs) nt;};
|
||||||
in {
|
in {
|
||||||
options = {
|
options = {
|
||||||
base = lib.mkOption {
|
base = lib.mkOption {
|
||||||
|
|
@ -49,6 +57,18 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
homeManager = mkOption {
|
||||||
|
type = types.nullOr flakeRef;
|
||||||
|
default = null;
|
||||||
|
example = lib.literalExpression "inputs.home-manager";
|
||||||
|
description = ''
|
||||||
|
The path to the home-manager source. A `homeManager` flake reference
|
||||||
|
is required to be set for `homes/` to be evaluated, and can be specified via either:
|
||||||
|
1. `options.nodes.homeManager` (default `homManager` used for all systems)
|
||||||
|
2. `options.nodes.nodes.<name>.homeManager` (takes prescedence over `options.nodes.homeManager`)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
modules = mkOption {
|
modules = mkOption {
|
||||||
type = types.listOf types.raw;
|
type = types.listOf types.raw;
|
||||||
default = [];
|
default = [];
|
||||||
|
|
@ -67,15 +87,28 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
homeManager = mkOption {
|
groups = mkOption {
|
||||||
type = types.nullOr flakeRef;
|
type = types.attrs;
|
||||||
default = null;
|
default = {};
|
||||||
example = lib.literalExpression "inputs.home-manager";
|
example = lib.literalExpression "{ servers = { staging = {}; production = {}; }; }";
|
||||||
description = ''
|
description = ''
|
||||||
The path to the home-manager source. A `homeManager` flake reference
|
Hierarchical groups that nodes can be a member of.
|
||||||
is required to be set for `homes/` to be evaluated, and can be specified via either:
|
'';
|
||||||
1. `options.nodes.homeManager` (default `homManager` used for all systems)
|
|
||||||
2. `options.nodes.nodes.<name>.homeManager` (takes prescedence over `options.nodes.homeManager`)
|
apply = groupLibs.parseGroupsDecl;
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes = mkOption {
|
||||||
|
type = types.attrsOf (types.submoduleWith {
|
||||||
|
specialArgs =
|
||||||
|
specialArgs
|
||||||
|
// {
|
||||||
|
nodeConfig = config;
|
||||||
|
};
|
||||||
|
modules = [./node.nix];
|
||||||
|
});
|
||||||
|
description = ''
|
||||||
|
Node (host systems) declarations.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
checks =
|
{
|
||||||
inputs.deploy-rs.lib
|
config,
|
||||||
|> mapAttrs (system: deployLib:
|
_snow,
|
||||||
deployLib.deployChecks deploy);
|
...
|
||||||
|
}: {
|
||||||
|
outputs.checks =
|
||||||
|
_snow.inputs.deploy-rs.lib
|
||||||
|
|> builtins.mapAttrs (system: deployLib:
|
||||||
|
deployLib.deployChecks config.outputs.deploy);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,90 @@
|
||||||
deploy.nodes = mapNodes nodes ({
|
{
|
||||||
name,
|
_snow,
|
||||||
node,
|
config,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(node.deploy)
|
(builtins)
|
||||||
ssh
|
mapAttrs
|
||||||
user
|
;
|
||||||
interactiveSudo
|
|
||||||
remoteBuild
|
|
||||||
rollback
|
|
||||||
autoRollback
|
|
||||||
magicRollback
|
|
||||||
activationTimeout
|
|
||||||
confirmTimeout
|
|
||||||
;
|
|
||||||
|
|
||||||
nixosFor = system: inputs.deploy-rs.lib.${system}.activate.nixos;
|
mapNodes = nodes: f:
|
||||||
in {
|
nodes.nodes
|
||||||
hostname =
|
|> mapAttrs (name: node: let
|
||||||
if ssh.host != null
|
# use per-node base or default to nodes' base
|
||||||
then ssh.host
|
base =
|
||||||
else "";
|
if node.base != null
|
||||||
|
then node.base
|
||||||
|
else if nodes.base != null
|
||||||
|
then nodes.base
|
||||||
|
else
|
||||||
|
abort ''
|
||||||
|
snow cannot construct nodes node "${name}" without a base package source.
|
||||||
|
Ensure `nodes.nodes.*.base` or `nodes.base` is a flake reference to the github:NixOS/nixpkgs repository.
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
f rec {
|
||||||
|
inherit name node base;
|
||||||
|
inherit (base) lib;
|
||||||
|
|
||||||
profilesOrder = ["default"]; # profiles priority
|
groups = node.groups (parseGroupsDecl nodes.groups);
|
||||||
profiles.default = {
|
groupModules = root: getGroupModules root groups;
|
||||||
path = nixosFor node.system nixosConfigurations.${name};
|
});
|
||||||
|
in {
|
||||||
|
outputs.deploy.nodes = mapNodes config.nodes ({
|
||||||
|
name,
|
||||||
|
node,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(node.deploy)
|
||||||
|
ssh
|
||||||
|
user
|
||||||
|
interactiveSudo
|
||||||
|
remoteBuild
|
||||||
|
rollback
|
||||||
|
autoRollback
|
||||||
|
magicRollback
|
||||||
|
activationTimeout
|
||||||
|
confirmTimeout
|
||||||
|
;
|
||||||
|
|
||||||
user = user;
|
nixosFor = system: _snow.inputs.deploy-rs.lib.${system}.activate.nixos;
|
||||||
sudo = "sudo -u";
|
in {
|
||||||
interactiveSudo = interactiveSudo;
|
hostname =
|
||||||
|
if ssh.host != null
|
||||||
|
then ssh.host
|
||||||
|
else "";
|
||||||
|
|
||||||
fastConnection = false;
|
profilesOrder = ["default"]; # profiles priority
|
||||||
|
profiles.default = {
|
||||||
|
path = nixosFor node.system config.outputs.nixosConfigurations.${name};
|
||||||
|
|
||||||
autoRollback = autoRollback -> rollback;
|
user = user;
|
||||||
magicRollback = magicRollback -> rollback;
|
sudo = "sudo -u";
|
||||||
activationTimeout = activationTimeout;
|
interactiveSudo = interactiveSudo;
|
||||||
confirmTimeout = confirmTimeout;
|
|
||||||
|
|
||||||
remoteBuild = remoteBuild;
|
fastConnection = false;
|
||||||
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"]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
|
autoRollback = autoRollback -> rollback;
|
||||||
|
magicRollback = magicRollback -> rollback;
|
||||||
|
activationTimeout = activationTimeout;
|
||||||
|
confirmTimeout = confirmTimeout;
|
||||||
|
|
||||||
|
remoteBuild = remoteBuild;
|
||||||
|
sshUser = ssh.user;
|
||||||
|
sshOpts =
|
||||||
|
ssh.opts
|
||||||
|
++ (
|
||||||
|
if builtins.elem "-p" ssh.opts
|
||||||
|
then []
|
||||||
|
else ["-p" (toString ssh.port)]
|
||||||
|
)
|
||||||
|
++ (
|
||||||
|
if builtins.elem "-A" ssh.opts
|
||||||
|
then []
|
||||||
|
else ["-A"]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,60 +8,78 @@
|
||||||
# options = { ... };
|
# options = { ... };
|
||||||
# type = { ... };
|
# type = { ... };
|
||||||
# }
|
# }
|
||||||
nixosConfigurations = mapNodes nodes (
|
{
|
||||||
{
|
snow,
|
||||||
base,
|
config,
|
||||||
lib,
|
systems,
|
||||||
name,
|
root,
|
||||||
node,
|
...
|
||||||
groupModules,
|
}: let
|
||||||
...
|
inherit
|
||||||
}: let
|
(builtins)
|
||||||
homeManager =
|
all
|
||||||
if node.homeManager != null
|
attrNames
|
||||||
then node.homeManager
|
warn
|
||||||
else if nodes.homeManager != null
|
;
|
||||||
then nodes.homeManager
|
|
||||||
else
|
|
||||||
warn ''
|
|
||||||
[snowflake] Neither `nodes.homeManager` nor `nodes.nodes.${name}.homeManager` were specified!
|
|
||||||
[snowflake] home-manager will NOT be used! User configuration will be ignored!
|
|
||||||
''
|
|
||||||
null;
|
|
||||||
|
|
||||||
userArgs = nodes.args // node.args;
|
inherit
|
||||||
ceruleanArgs = {
|
(config)
|
||||||
inherit systems root base nodes node;
|
nodes
|
||||||
inherit (node) system;
|
;
|
||||||
inherit (this) snow;
|
in {
|
||||||
hostname = name;
|
outputs.nixosConfigurations = mapNodes nodes (
|
||||||
|
{
|
||||||
|
base,
|
||||||
|
lib,
|
||||||
|
name,
|
||||||
|
node,
|
||||||
|
groupModules,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
homeManager =
|
||||||
|
if node.homeManager != null
|
||||||
|
then node.homeManager
|
||||||
|
else if nodes.homeManager != null
|
||||||
|
then nodes.homeManager
|
||||||
|
else
|
||||||
|
warn ''
|
||||||
|
[snowflake] Neither `nodes.homeManager` nor `nodes.nodes.${name}.homeManager` were specified!
|
||||||
|
[snowflake] home-manager will NOT be used! User configuration will be ignored!
|
||||||
|
''
|
||||||
|
null;
|
||||||
|
|
||||||
_cerulean = {
|
userArgs = nodes.args // node.args;
|
||||||
inherit inputs userArgs ceruleanArgs homeManager;
|
snowArgs = {
|
||||||
specialArgs = userArgs // ceruleanArgs;
|
inherit systems snow root base nodes node;
|
||||||
};
|
inherit (node) system;
|
||||||
|
hostname = name;
|
||||||
|
|
||||||
|
_snow = {
|
||||||
|
inherit inputs userArgs snowArgs homeManager;
|
||||||
|
specialArgs = userArgs // snowArgs;
|
||||||
};
|
};
|
||||||
specialArgs = assert (userArgs
|
};
|
||||||
|> attrNames
|
specialArgs = assert (userArgs
|
||||||
|> all (argName:
|
|> attrNames
|
||||||
! ceruleanArgs ? argName
|
|> all (argName:
|
||||||
|| abort ''
|
! snowArgs ? argName
|
||||||
`specialArgs` are like super important to Cerulean my love... </3
|
|| abort ''
|
||||||
But `args.${argName}` is a reserved argument name :(
|
`specialArgs` are like super important to Snow my love... </3
|
||||||
''));
|
But `args.${argName}` is a reserved argument name :(
|
||||||
ceruleanArgs._cerulean.specialArgs;
|
''));
|
||||||
in
|
snowArgs._snow.specialArgs;
|
||||||
lib.nixosSystem {
|
in
|
||||||
inherit (node) system;
|
lib.nixosSystem {
|
||||||
inherit specialArgs;
|
inherit (node) system;
|
||||||
modules =
|
inherit specialArgs;
|
||||||
[
|
modules =
|
||||||
self.nixosModules.default
|
[
|
||||||
(this.findImport /${root}/hosts/${name})
|
snow.nixosModules.default
|
||||||
]
|
(snow.findImport /${root}/hosts/${name})
|
||||||
++ (groupModules root)
|
]
|
||||||
++ node.modules
|
++ (groupModules root)
|
||||||
++ nodes.modules;
|
++ node.modules
|
||||||
}
|
++ nodes.modules;
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
27
nix/snow/lib/default.nix
Normal file
27
nix/snow/lib/default.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright 2025-2026 _cry64 (Emile Clark-Boman)
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
{
|
||||||
|
nt,
|
||||||
|
mix,
|
||||||
|
...
|
||||||
|
} @ args: let
|
||||||
|
inherit (nt) findImport;
|
||||||
|
in
|
||||||
|
mix.newMixture args (mixture: {
|
||||||
|
includes.public = [
|
||||||
|
./nixpkgs.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
inherit findImport;
|
||||||
|
})
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
revInfo ? "",
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
|
|
@ -12,11 +13,12 @@
|
||||||
# override it at all.
|
# override it at all.
|
||||||
minVersion = "23.05pre-git";
|
minVersion = "23.05pre-git";
|
||||||
|
|
||||||
isNixpkgsValidVersion =
|
isNixpkgsValidVersion = let
|
||||||
|
revInfo = lib.optional (inputs.nixpkgs?rev) " (nixpkgs-lib.rev: ${inputs.nixpkgs.rev})";
|
||||||
|
in
|
||||||
(builtins.compareVersions lib.version minVersion < 0)
|
(builtins.compareVersions lib.version minVersion < 0)
|
||||||
# XXX: TODO: make this message snow specific
|
|
||||||
|| abort ''
|
|| abort ''
|
||||||
The nixpkgs-lib dependency of flake-parts was overridden but is too old.
|
The nixpkgs dependency of snow was overridden but is too old.
|
||||||
The minimum supported version of nixpkgs-lib is ${minVersion},
|
The minimum supported version of nixpkgs-lib is ${minVersion},
|
||||||
but the actual version is ${lib.version}${revInfo}.
|
but the actual version is ${lib.version}${revInfo}.
|
||||||
'';
|
'';
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
# Copyright 2025-2026 _cry64 (Emile Clark-Boman)
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
{nt, ...}: let
|
|
||||||
inherit
|
|
||||||
(builtins)
|
|
||||||
concatLists
|
|
||||||
elem
|
|
||||||
filter
|
|
||||||
isAttrs
|
|
||||||
mapAttrs
|
|
||||||
pathExists
|
|
||||||
typeOf
|
|
||||||
;
|
|
||||||
|
|
||||||
rootGroupName = "all";
|
|
||||||
|
|
||||||
parseGroupsDecl = groups: let
|
|
||||||
validGroup = g:
|
|
||||||
isAttrs g
|
|
||||||
|| throw ''
|
|
||||||
Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead!
|
|
||||||
Ensure all the group definitions are attribute sets under your call to `cerulean.mkNexus`.
|
|
||||||
NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP`
|
|
||||||
'';
|
|
||||||
delegate = parent: gName: g: let
|
|
||||||
result =
|
|
||||||
(g
|
|
||||||
// {
|
|
||||||
_name = gName;
|
|
||||||
_parent = parent;
|
|
||||||
})
|
|
||||||
|> mapAttrs (name: value:
|
|
||||||
if elem name ["_name" "_parent"]
|
|
||||||
# ignore metadata fields
|
|
||||||
then value
|
|
||||||
else assert validGroup value; (delegate result name value));
|
|
||||||
in
|
|
||||||
result;
|
|
||||||
in
|
|
||||||
assert validGroup groups;
|
|
||||||
delegate null rootGroupName groups;
|
|
||||||
|
|
||||||
getGroupModules = root: groups:
|
|
||||||
# ensure root group is always added
|
|
||||||
groups
|
|
||||||
# add all inherited groups via _parent
|
|
||||||
|> map (let
|
|
||||||
delegate = g:
|
|
||||||
if g._parent == null
|
|
||||||
then [g]
|
|
||||||
else [g] ++ delegate (g._parent);
|
|
||||||
in
|
|
||||||
delegate)
|
|
||||||
# flatten recursion result
|
|
||||||
|> concatLists
|
|
||||||
# find import location
|
|
||||||
|> map (group: nt.findImport /${root}/groups/${group._name})
|
|
||||||
# filter by uniqueness
|
|
||||||
|> nt.prim.unique
|
|
||||||
# ignore missing groups
|
|
||||||
|> filter pathExists;
|
|
||||||
in {
|
|
||||||
mapNodes = nodes: f:
|
|
||||||
nodes.nodes
|
|
||||||
|> mapAttrs (name: node: let
|
|
||||||
# use per-node base or default to nodes' base
|
|
||||||
base =
|
|
||||||
if node.base != null
|
|
||||||
then node.base
|
|
||||||
else if nodes.base != null
|
|
||||||
then nodes.base
|
|
||||||
else
|
|
||||||
abort ''
|
|
||||||
Cerulean cannot construct nodes node "${name}" without a base package source.
|
|
||||||
Ensure `nodes.nodes.*.base` or `nodes.base` is a flake reference to the github:NixOS/nixpkgs repository.
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
f rec {
|
|
||||||
inherit name node base;
|
|
||||||
inherit (base) lib;
|
|
||||||
|
|
||||||
groups = node.groups (parseGroupsDecl nodes.groups);
|
|
||||||
groupModules = root: getGroupModules root groups;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue