minimal working state

This commit is contained in:
do butterflies cry? 2026-03-17 20:37:59 +10:00
parent 855430ef16
commit 6c1a0a5d33
Signed by: cry
GPG key ID: F68745A836CA0412
26 changed files with 331 additions and 326 deletions

32
flake.lock generated
View file

@ -9,11 +9,11 @@
"utils": "utils"
},
"locked": {
"lastModified": 1766051518,
"narHash": "sha256-znKOwPXQnt3o7lDb3hdf19oDo0BLP4MfBOYiWkEHoik=",
"lastModified": 1770019181,
"narHash": "sha256-hwsYgDnby50JNVpTRYlF3UR/Rrpt01OrxVuryF40CFY=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "d5eff7f948535b9c723d60cd8239f8f11ddc90fa",
"rev": "77c906c0ba56aabdbc72041bf9111b565cdd6171",
"type": "github"
},
"original": {
@ -68,11 +68,11 @@
"spectrum": "spectrum"
},
"locked": {
"lastModified": 1771365290,
"narHash": "sha256-1XJOslVyF7yzf6yd/yl1VjGLywsbtwmQh3X1LuJcLI4=",
"lastModified": 1773018425,
"narHash": "sha256-fpgZBmZpKoEXEowBK/6m8g9FcOLWQ4UxhXHqCw2CpSM=",
"owner": "microvm-nix",
"repo": "microvm.nix",
"rev": "789c90b164b55b4379e7a94af8b9c01489024c18",
"rev": "25ebda3c558e923720c965832dc9a04f559a055c",
"type": "github"
},
"original": {
@ -129,11 +129,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1768323494,
"narHash": "sha256-yBXJLE6WCtrGo7LKiB6NOt6nisBEEkguC/lq/rP3zRQ=",
"lastModified": 1773375660,
"narHash": "sha256-SEzUWw2Rf5Ki3bcM26nSKgbeoqi2uYy8IHVBqOKjX3w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2c3e5ec5df46d3aeee2a1da0bfedd74e21f4bf3a",
"rev": "3e20095fe3c6cbb1ddcef89b26969a69a1570776",
"type": "github"
},
"original": {
@ -166,11 +166,11 @@
"systems": "systems_2"
},
"locked": {
"lastModified": 1770975056,
"narHash": "sha256-ZXTz/P3zUbbM6lNXzt91u8EwfNqhXpYMu8+wvFZqQHE=",
"lastModified": 1773738366,
"narHash": "sha256-oH22HyNHEdCoCQo734sQCHUr6C0jmGQJMZ13dsgEHkk=",
"owner": "cry128",
"repo": "nt",
"rev": "f42dcdd49a7921a7f433512e83d5f93696632412",
"rev": "f32c3a726a3d608d30aaaa1df2301c1eaf5ef8f4",
"type": "github"
},
"original": {
@ -212,11 +212,11 @@
"spectrum": {
"flake": false,
"locked": {
"lastModified": 1759482047,
"narHash": "sha256-H1wiXRQHxxPyMMlP39ce3ROKCwI5/tUn36P8x6dFiiQ=",
"lastModified": 1772189877,
"narHash": "sha256-i1p90Rgssb//aNiTDFq46ZG/fk3LmyRLChtp/9lddyA=",
"ref": "refs/heads/main",
"rev": "c5d5786d3dc938af0b279c542d1e43bce381b4b9",
"revCount": 996,
"rev": "fe39e122d898f66e89ffa17d4f4209989ccb5358",
"revCount": 1255,
"type": "git",
"url": "https://spectrum-os.org/git/spectrum"
},

View file

@ -15,22 +15,28 @@
mix,
inputs,
...
} @ args:
mix.newMixture args (mixture: {
submods.public = [
./snow
];
} @ args: let
mixArgs =
args
// {
inherit (inputs.nixpkgs) lib;
};
in
mix.newMixture mixArgs (mixture: {
submods.public = [
./snow
];
version = "0.2.6-alpha";
version = "0.2.6-alpha";
overlays = [
# build deploy-rs as a package not from the flake input,
# hence we can rely on a nixpkg binary cache.
inputs.deploy-rs.overlays.default
];
overlays = [
# build deploy-rs as a package not from the flake input,
# hence we can rely on a nixpkg binary cache.
inputs.deploy-rs.overlays.default
];
nixosModules = rec {
default = cerulean;
cerulean = ./nixos;
};
})
nixosModules = rec {
default = cerulean;
cerulean = ./nixos;
};
})

View file

@ -18,13 +18,13 @@
node,
pkgs,
lib,
_cerulean,
_snow,
...
} @ args: {
imports =
[
_cerulean.inputs.sops-nix.nixosModules.sops
# _cerulean.inputs.microvm.nixosModules.microvm
_snow.inputs.sops-nix.nixosModules.sops
# _snow.inputs.microvm.nixosModules.microvm
# add support for `options.legacyImports`
# ./legacy-imports.nix
@ -36,7 +36,7 @@
(import /${root}/nixpkgs.nix)
]
# homemanager options declarations
++ (lib.optional (_cerulean.homeManager != null) ./home.nix)
++ (lib.optional (_snow.homeManager != null) ./home.nix)
# remote deployment configuration
++ (lib.optional (node.deploy.ssh.host != null) ./remote-deploy);
@ -46,7 +46,7 @@
(with pkgs; [
sops
])
++ (with _cerulean.inputs; [
++ (with _snow.inputs; [
deploy-rs.packages.${system}.default
]);
}

View file

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
{
_cerulean,
_snow,
config,
root,
lib,
@ -30,7 +30,7 @@
;
in {
imports = [
_cerulean.homeManager.nixosModules.default
_snow.homeManager.nixosModules.default
];
options = {
@ -69,7 +69,7 @@ in {
_module.args.username = name;
});
extraSpecialArgs = _cerulean.specialArgs;
extraSpecialArgs = _snow.specialArgs;
sharedModules = [
../home

View file

@ -11,12 +11,17 @@
# 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.
{mix, ...} @ args:
mix.newMixture args (mixture: {
includes = {
public = [
./flake
./lib
];
};
{
nt,
mix,
...
} @ args:
mix.newMixture (removeAttrs args ["this"]) (mixture: {
submods.public = [
./lib
];
includes.public = [
./flake
];
})

View file

@ -1,4 +1,5 @@
{
self,
this,
inputs,
systems,
@ -16,14 +17,20 @@
inherit (inputs.nixpkgs) lib;
in {
# snow.flake
# XXX: TODO: stop taking in root as parameter (maybe take self instead?)
flake = flakeInputs: root: let
snowflake = lib.evalModules {
class = "snowflake";
specialArgs = let
reservedSpecialArgs = {
inherit (this) snow;
# inherit (this) snow;
snow = this;
inherit systems root;
inputs = flakeInputs;
_snowFlake = {
inherit self inputs;
};
};
warnIfReserved = let
@ -50,7 +57,10 @@ in {
flakeInputs // reservedSpecialArgs;
modules = [
./module.nix
./nodes
./modules
./outputs
(this.lib.findImport /${root}/snow)
];
};
in

View file

@ -1,24 +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.
{
root,
snow,
...
}: {
imports = [
./nodes
./modules
(snow.findImport /${root}/snow)
];
}

View file

@ -10,7 +10,7 @@
;
inherit
(snow)
(snow.lib)
mkPerSystemFlakeOutput
;

View file

@ -9,7 +9,7 @@
types
;
inherit
(snow)
(snow.lib)
mkPerSystemFlakeOutput
;
in

View file

@ -10,7 +10,7 @@
literalExpression
;
inherit
(snow)
(snow.lib)
mkPerSystemFlakeOutput
;
in

View file

@ -9,7 +9,7 @@
types
;
inherit
(snow)
(snow.lib)
mkPerSystemFlakeOutput
;
in

View file

@ -9,7 +9,7 @@
types
;
inherit
(snow)
(snow.lib)
mkPerSystemFlakeOutput
;
in

View file

@ -8,38 +8,39 @@
mkOption
types
;
outputs = mkOption {
type = types.submoduleWith {
modules = [
{
freeformType =
types.lazyAttrsOf
(types.unique
{
message = ''
No option has been declared for this flake output attribute, so its definitions can't be merged automatically.
Possible solutions:
- Load a module that defines this flake output attribute
- Declare an option for this flake output attribute
- Make sure the output attribute is spelled correctly
- Define the value only once, with a single definition in a single module
'';
}
types.raw);
}
];
};
description = ''
Raw flake output attributes. Any attribute can be set here, but some
attributes are represented by options, to provide appropriate
configuration merging.
'';
};
in {
options = {
inherit outputs;
outputs = mkOption {
type = types.submoduleWith {
modules = [
{
freeformType =
types.lazyAttrsOf
(types.unique
{
message = ''
No option has been declared for this flake output attribute, so its definitions can't be merged automatically.
Possible solutions:
- Load a module that defines this flake output attribute
- Declare an option for this flake output attribute
- Make sure the output attribute is spelled correctly
- Define the value only once, with a single definition in a single module
'';
}
types.raw);
}
];
};
description = ''
Raw flake output attributes. Any attribute can be set here, but some
attributes are represented by options, to provide appropriate
configuration merging.
'';
};
};
config = {inherit (config) flake;};
config = {
# ensure a minimal version is set
outputs = {};
};
}

View file

@ -10,7 +10,7 @@
;
inherit
(snow)
(snow.lib)
mkPerSystemFlakeOutput
;
in

View file

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
{
_snow,
_snowFlake,
lib,
specialArgs,
...
@ -39,7 +39,7 @@
config = {
nodes = {
base = _snow.inputs.nixpkgs;
base = _snowFlake.inputs.nixpkgs;
};
};
}

View file

@ -1,73 +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";
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;
}

View file

@ -14,8 +14,9 @@
{
lib,
systems,
config,
nodesConfig,
groups,
groupLibs,
...
}: {
options = let
@ -25,6 +26,11 @@
types
;
inherit
(groupLibs)
resolveGroupsInheritance
;
flakeRef = types.either types.str types.path;
in {
enabled = lib.mkOption {
@ -113,8 +119,8 @@
A function from the `groups` hierarchy to a list of groups this node inherits from.
'';
apply = groupsFn:
groupsFn nodesConfig.groups;
# apply = groupsFn:
# groupsFn nodesConfig.groups |> resolveGroupsInheritance;
};
deploy = {
@ -266,32 +272,32 @@
};
};
config = let
throwGotNull = name:
throw ''
[snow] `nodes.<name>.${name}` must be set for all nodes! (got: <null>)
'';
givenSystem =
(config.system != null)
|| throwGotNull "system";
# 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";
# givenBase =
# (config.base != null)
# || throwGotNull "base";
givenHomeManager =
(config.homeManager != null)
|| throwGotNull "homeManager";
# 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;
};
# 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;
# };
}

View file

@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
{
_snow,
_snowFlake,
snow,
root,
lib,
config,
specialArgs,
@ -26,7 +28,10 @@
flakeRef = types.either types.str types.path;
groupLibs = import ./groups.nix {inherit (_snow.inputs) nt;};
groupLibs = import ./groups.nix {
inherit snow root;
inherit (_snowFlake.inputs) nt;
};
in {
options = {
base = lib.mkOption {
@ -94,8 +99,6 @@ in {
description = ''
Hierarchical groups that nodes can be a member of.
'';
apply = groupLibs.parseGroupsDecl;
};
nodes = mkOption {
@ -103,7 +106,8 @@ in {
specialArgs =
specialArgs
// {
nodeConfig = config;
nodesConfig = config;
inherit groupLibs;
};
modules = [./node.nix];
});

View file

@ -1,10 +1,10 @@
{
config,
_snow,
_snowFlake,
...
}: {
outputs.checks =
_snow.inputs.deploy-rs.lib
_snowFlake.inputs.deploy-rs.lib
|> builtins.mapAttrs (system: deployLib:
deployLib.deployChecks config.outputs.deploy);
}

View file

@ -0,0 +1,7 @@
{...}: {
imports = [
./checks.nix
./deploy.nix
./nixosConfigurations.nix
];
}

View file

@ -1,37 +1,10 @@
{
_snow,
_snowFlake,
snow,
config,
...
}: let
inherit
(builtins)
mapAttrs
;
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 ''
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;
groups = node.groups (parseGroupsDecl nodes.groups);
groupModules = root: getGroupModules root groups;
});
in {
outputs.deploy.nodes = mapNodes config.nodes ({
}: {
outputs.deploy.nodes = snow.lib.mapNodes config.nodes ({
name,
node,
...
@ -49,7 +22,7 @@ in {
confirmTimeout
;
nixosFor = system: _snow.inputs.deploy-rs.lib.${system}.activate.nixos;
nixosFor = system: _snowFlake.inputs.deploy-rs.lib.${system}.activate.nixos;
in {
hostname =
if ssh.host != null

View file

@ -1,14 +1,5 @@
# {
# _module = { ... };
# _type = "configuration";
# class = null;
# config = { ... };
# extendModules = «lambda extendModules @ /nix/store/9hfp0agnm43kz72l5lpfn9var5p0x2fa-source/lib/modules.nix:340:9»;
# graph = [ ... ];
# options = { ... };
# type = { ... };
# }
{
_snowFlake,
snow,
config,
systems,
@ -27,59 +18,67 @@
nodes
;
in {
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;
outputs.nixosConfigurations = let
groups = snow.lib.parseGroupDecls root config.nodes.groups;
in
snow.lib.mapNodes nodes (
{
base,
lib,
name,
node,
...
}: let
nodeGroups =
(node.groups groups)
|> snow.lib.resolveGroupsInheritance
|> snow.lib.groupModules;
userArgs = nodes.args // node.args;
snowArgs = {
inherit systems snow root base nodes node;
inherit (node) system;
hostname = name;
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;
_snow = {
inherit inputs userArgs snowArgs homeManager;
specialArgs = userArgs // snowArgs;
userArgs = nodes.args // node.args;
snowArgs = {
inherit systems snow root base nodes node;
inherit (node) system;
hostname = name;
_snow = {
inherit (_snowFlake) inputs;
inherit userArgs snowArgs homeManager;
specialArgs = userArgs // snowArgs;
};
};
};
specialArgs = assert (userArgs
|> attrNames
|> all (argName:
! snowArgs ? argName
|| abort ''
`specialArgs` are like super important to Snow my love... </3
But `args.${argName}` is a reserved argument name :(
''));
snowArgs._snow.specialArgs;
in
lib.nixosSystem {
inherit (node) system;
inherit specialArgs;
modules =
[
snow.nixosModules.default
(snow.findImport /${root}/hosts/${name})
]
++ (groupModules root)
++ node.modules
++ nodes.modules;
}
);
specialArgs = assert (userArgs
|> attrNames
|> all (argName:
! snowArgs ? argName
|| abort ''
`specialArgs` are like super important to Snow my love... </3
But `args.${argName}` is a reserved argument name :(
''));
snowArgs._snow.specialArgs;
in
lib.nixosSystem {
inherit (node) system;
inherit specialArgs;
modules =
[
_snowFlake.self.nixosModules.default
(snow.lib.findImport /${root}/hosts/${name})
]
++ nodeGroups
++ node.modules
++ nodes.modules;
}
);
}

View file

@ -15,13 +15,11 @@
nt,
mix,
...
} @ args: let
inherit (nt) findImport;
in
mix.newMixture args (mixture: {
includes.public = [
./nixpkgs.nix
];
inherit findImport;
})
} @ args:
mix.newMixture args (mixture: {
includes.public = [
./util.nix
./nixpkgs.nix
./nodes.nix
];
})

View file

@ -14,9 +14,12 @@
minVersion = "23.05pre-git";
isNixpkgsValidVersion = let
revInfo = lib.optional (inputs.nixpkgs?rev) " (nixpkgs-lib.rev: ${inputs.nixpkgs.rev})";
revInfo =
if inputs.nixpkgs?rev
then " (nixpkgs-lib.rev: ${inputs.nixpkgs.rev})"
else "";
in
(builtins.compareVersions lib.version minVersion < 0)
(builtins.compareVersions lib.version minVersion >= 0)
|| abort ''
The nixpkgs dependency of snow was overridden but is too old.
The minimum supported version of nixpkgs-lib is ${minVersion},

87
nix/snow/lib/nodes.nix Normal file
View file

@ -0,0 +1,87 @@
{
this,
nt,
...
}: let
inherit
(builtins)
concatLists
elem
filter
isAttrs
mapAttrs
pathExists
typeOf
;
inherit (nt.prim) uniq;
rootGroupName = "all";
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 ''
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;
inherit (node) groups;
});
groupModules = map (group: group._module);
parseGroupDecls = root: groupDecls: 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`.
'';
delegate = parent: gName: g: let
result =
(g
// {
_name = gName;
_parent = parent;
_module = this.lib.findImport /${root}/groups/${gName};
})
|> mapAttrs (name: value:
if elem name ["_name" "_parent" "_module"]
# ignore metadata fields
then value
else assert validGroup value; (delegate result name value));
in
result;
in
assert validGroup groupDecls;
delegate null rootGroupName groupDecls;
resolveGroupsInheritance = groups:
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
# ignore missing groups
|> filter (group: pathExists group._module)
# filter by uniqueness
|> uniq;
}

3
nix/snow/lib/util.nix Normal file
View file

@ -0,0 +1,3 @@
{nt, ...}: {
inherit (nt) findImport;
}