support snow.flake
This commit is contained in:
parent
e4ab4f4b5a
commit
45c53f025a
10 changed files with 352 additions and 41 deletions
|
|
@ -20,12 +20,10 @@ mix.newMixture args (mixture: {
|
||||||
includes.public = [
|
includes.public = [
|
||||||
./nexus
|
./nexus
|
||||||
];
|
];
|
||||||
|
submods.public = [
|
||||||
|
./snow
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
nixosModules = rec {
|
|
||||||
default = cerulean;
|
|
||||||
cerulean = ./nixos;
|
|
||||||
};
|
|
||||||
version = "0.2.3";
|
version = "0.2.3";
|
||||||
|
|
||||||
overlays = [
|
overlays = [
|
||||||
|
|
@ -33,4 +31,9 @@ mix.newMixture args (mixture: {
|
||||||
# hence we can rely on a nixpkg binary cache.
|
# hence we can rely on a nixpkg binary cache.
|
||||||
inputs.deploy-rs.overlays.default
|
inputs.deploy-rs.overlays.default
|
||||||
];
|
];
|
||||||
|
|
||||||
|
nixosModules = rec {
|
||||||
|
default = cerulean;
|
||||||
|
cerulean = ./nixos;
|
||||||
|
};
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,11 @@
|
||||||
# options declarations
|
# options declarations
|
||||||
(import ./nixpkgs.nix (args // {contextName = "hosts";}))
|
(import ./nixpkgs.nix (args // {contextName = "hosts";}))
|
||||||
]
|
]
|
||||||
++ lib.optional (_cerulean.homeManager != null) [./home-manager.nix];
|
++ (
|
||||||
|
if _cerulean.homeManager != null
|
||||||
|
then [./home-manager.nix]
|
||||||
|
else []
|
||||||
|
);
|
||||||
|
|
||||||
environment.systemPackages = with _cerulean.inputs; [
|
environment.systemPackages = with _cerulean.inputs; [
|
||||||
deploy-rs.packages.${system}.default
|
deploy-rs.packages.${system}.default
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@
|
||||||
pathExists
|
pathExists
|
||||||
;
|
;
|
||||||
in {
|
in {
|
||||||
|
imports = [
|
||||||
|
_cerulean.homeManager.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
users =
|
users =
|
||||||
config.users.users
|
config.users.users
|
||||||
|
|
|
||||||
|
|
@ -75,19 +75,19 @@ in {
|
||||||
# NOTE: _module.args is a special option that allows us to
|
# NOTE: _module.args is a special option that allows us to
|
||||||
# NOTE: set extend specialArgs from inside the modules.
|
# NOTE: set extend specialArgs from inside the modules.
|
||||||
# WARNING: pkgs is a reserved specialArg
|
# WARNING: pkgs is a reserved specialArg
|
||||||
_module.args = removeAttrs repos ["pkgs"];
|
_module.args = removeAttrs repos ["pkgs" "default"];
|
||||||
|
|
||||||
nixpkgs =
|
nixpkgs =
|
||||||
if contextName == "hosts"
|
if contextName == "hosts"
|
||||||
then {
|
then {
|
||||||
flake.source = lib.mkOverride 200 base; # DEBUG: temp while getting base to work
|
flake.source = lib.mkForce base; # DEBUG: temp while getting base to work
|
||||||
overlays = lib.mkOverride 200 (defaultPkgs.overlays or {});
|
overlays = lib.mkForce (defaultPkgs.overlays or {});
|
||||||
config = lib.mkOverride 200 (defaultPkgs.config or {});
|
config = lib.mkForce (defaultPkgs.config or {});
|
||||||
}
|
}
|
||||||
else if contextName == "homes"
|
else if contextName == "homes"
|
||||||
then {
|
then {
|
||||||
config = lib.mkOverride 200 (defaultPkgs.config or {});
|
config = lib.mkForce (defaultPkgs.config or {});
|
||||||
overlays = lib.mkOverride 200 (defaultPkgs.overlays or []);
|
overlays = lib.mkForce (defaultPkgs.overlays or []);
|
||||||
}
|
}
|
||||||
else {};
|
else {};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,174 @@
|
||||||
# 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.
|
||||||
{...}: {
|
{
|
||||||
imports = [
|
this,
|
||||||
./nodes
|
self,
|
||||||
];
|
inputs,
|
||||||
}
|
systems,
|
||||||
|
nt,
|
||||||
|
mix,
|
||||||
|
...
|
||||||
|
} @ args: let
|
||||||
|
inherit
|
||||||
|
(builtins)
|
||||||
|
all
|
||||||
|
attrNames
|
||||||
|
elem
|
||||||
|
mapAttrs
|
||||||
|
warn
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (inputs.nixpkgs) lib;
|
||||||
|
|
||||||
|
inherit (nt) findImport;
|
||||||
|
in
|
||||||
|
mix.newMixture args (mixture: let
|
||||||
|
inherit (mixture) mapNodes;
|
||||||
|
in {
|
||||||
|
includes.private = [
|
||||||
|
./lib/nodes.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
inherit findImport;
|
||||||
|
|
||||||
|
# snow.flake
|
||||||
|
flake = flakeInputs: root: let
|
||||||
|
module = lib.evalModules {
|
||||||
|
class = "snowflake";
|
||||||
|
# TODO: abort if inputs contains reserved names
|
||||||
|
specialArgs =
|
||||||
|
flakeInputs
|
||||||
|
// {
|
||||||
|
inherit root;
|
||||||
|
inherit systems;
|
||||||
|
inherit (this) snow; # please don't be infinite recursion...
|
||||||
|
inputs = flakeInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
./module.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes = module.config.nodes;
|
||||||
|
in rec {
|
||||||
|
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;
|
||||||
|
|
||||||
|
userArgs = nodes.args // node.args;
|
||||||
|
ceruleanArgs = {
|
||||||
|
inherit systems root base;
|
||||||
|
inherit (node) system;
|
||||||
|
inherit (this) snow;
|
||||||
|
|
||||||
|
_cerulean = {
|
||||||
|
inherit inputs userArgs ceruleanArgs homeManager;
|
||||||
|
specialArgs = userArgs // ceruleanArgs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
specialArgs = assert (userArgs
|
||||||
|
|> attrNames
|
||||||
|
|> all (argName:
|
||||||
|
! ceruleanArgs ? argName
|
||||||
|
|| abort ''
|
||||||
|
`specialArgs` are like super important to Cerulean my love... </3
|
||||||
|
But `args.${argName}` is a reserved argument name :(
|
||||||
|
''));
|
||||||
|
ceruleanArgs._cerulean.specialArgs;
|
||||||
|
in
|
||||||
|
lib.nixosSystem {
|
||||||
|
inherit (node) system;
|
||||||
|
inherit specialArgs;
|
||||||
|
modules =
|
||||||
|
[
|
||||||
|
self.nixosModules.default
|
||||||
|
(findImport (root + "/hosts/${name}"))
|
||||||
|
|
||||||
|
# inputs.microvm.nixosModules.microvm
|
||||||
|
]
|
||||||
|
++ (groupModules root)
|
||||||
|
++ node.modules
|
||||||
|
++ nodes.modules;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
deploy.nodes = mapNodes nodes ({
|
||||||
|
name,
|
||||||
|
node,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit
|
||||||
|
(node.deploy)
|
||||||
|
ssh
|
||||||
|
user
|
||||||
|
sudo
|
||||||
|
interactiveSudo
|
||||||
|
remoteBuild
|
||||||
|
rollback
|
||||||
|
autoRollback
|
||||||
|
magicRollback
|
||||||
|
activationTimeout
|
||||||
|
confirmTimeout
|
||||||
|
;
|
||||||
|
|
||||||
|
nixosFor = system: inputs.deploy-rs.lib.${system}.activate.nixos;
|
||||||
|
in {
|
||||||
|
hostname = ssh.host;
|
||||||
|
|
||||||
|
profilesOrder = ["default"]; # profiles priority
|
||||||
|
profiles.default = {
|
||||||
|
path = nixosFor node.system nixosConfigurations.${name};
|
||||||
|
|
||||||
|
user = user;
|
||||||
|
sudo = sudo;
|
||||||
|
interactiveSudo = interactiveSudo;
|
||||||
|
|
||||||
|
fastConnection = false;
|
||||||
|
|
||||||
|
autoRollback = autoRollback -> rollback;
|
||||||
|
magicRollback = magicRollback -> rollback;
|
||||||
|
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 =
|
||||||
|
inputs.deploy-rs.lib
|
||||||
|
|> mapAttrs (system: deployLib:
|
||||||
|
deployLib.deployChecks deploy);
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
|
||||||
96
cerulean/snow/lib/nodes.nix
Normal file
96
cerulean/snow/lib/nodes.nix
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
23
cerulean/snow/module.nix
Normal file
23
cerulean/snow/module.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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.
|
||||||
|
{
|
||||||
|
root,
|
||||||
|
snow,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
./nodes
|
||||||
|
(snow.findImport (root + "/snow"))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,11 @@
|
||||||
# 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, ...}: {
|
{
|
||||||
|
lib,
|
||||||
|
specialArgs,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
options.nodes = let
|
options.nodes = let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
|
|
@ -24,26 +28,35 @@
|
||||||
Cerulean node declarations.
|
Cerulean node declarations.
|
||||||
'';
|
'';
|
||||||
type = types.submoduleWith {
|
type = types.submoduleWith {
|
||||||
imports = [./shared.nix];
|
inherit specialArgs;
|
||||||
|
|
||||||
options = {
|
modules = [
|
||||||
groups = mkOption {
|
{
|
||||||
type = types.attrs;
|
imports = [./shared.nix];
|
||||||
default = {};
|
|
||||||
example = lib.literalExpression "{ servers = { staging = {}; production = {}; }; }";
|
|
||||||
description = ''
|
|
||||||
Hierarchical groups that nodes can be a member of.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
nodes = mkOption {
|
options = {
|
||||||
type = types.attrsOf (types.submoduleWith (import ./submodule.nix));
|
groups = mkOption {
|
||||||
# example = { ... }; # TODO
|
type = types.attrs;
|
||||||
description = ''
|
default = {};
|
||||||
Node (host systems) declarations.
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@
|
||||||
# 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.
|
||||||
{
|
{
|
||||||
inputs,
|
|
||||||
lib,
|
lib,
|
||||||
|
systems,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
imports = [./shared.nix];
|
imports = [./shared.nix];
|
||||||
|
|
@ -34,8 +34,8 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
system = types.nullOr mkOption {
|
system = mkOption {
|
||||||
type = types.enum inputs.systems;
|
type = types.nullOr (types.enum systems);
|
||||||
default = null;
|
default = null;
|
||||||
example = "x86_64-linux";
|
example = "x86_64-linux";
|
||||||
description = ''
|
description = ''
|
||||||
|
|
@ -44,8 +44,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
groups = mkOption {
|
groups = mkOption {
|
||||||
type = types.functionTo types.list;
|
# TODO: write a custom group type that validates better than types.attrs lol
|
||||||
default = [];
|
type = types.functionTo (types.listOf types.attrs);
|
||||||
|
default = groups: [];
|
||||||
example = lib.literalExpression "( groups: [ groups.servers groups.secure-boot ] )";
|
example = lib.literalExpression "( groups: [ groups.servers groups.secure-boot ] )";
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -43,5 +43,6 @@
|
||||||
{
|
{
|
||||||
inherit inputs self nt;
|
inherit inputs self nt;
|
||||||
inherit (nt) mix;
|
inherit (nt) mix;
|
||||||
|
systems = import inputs.systems;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue