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

View file

@ -15,8 +15,14 @@
mix, mix,
inputs, inputs,
... ...
} @ args: } @ args: let
mix.newMixture args (mixture: { mixArgs =
args
// {
inherit (inputs.nixpkgs) lib;
};
in
mix.newMixture mixArgs (mixture: {
submods.public = [ submods.public = [
./snow ./snow
]; ];
@ -33,4 +39,4 @@ mix.newMixture args (mixture: {
default = cerulean; default = cerulean;
cerulean = ./nixos; cerulean = ./nixos;
}; };
}) })

View file

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

View file

@ -12,7 +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.
{ {
_cerulean, _snow,
config, config,
root, root,
lib, lib,
@ -30,7 +30,7 @@
; ;
in { in {
imports = [ imports = [
_cerulean.homeManager.nixosModules.default _snow.homeManager.nixosModules.default
]; ];
options = { options = {
@ -69,7 +69,7 @@ in {
_module.args.username = name; _module.args.username = name;
}); });
extraSpecialArgs = _cerulean.specialArgs; extraSpecialArgs = _snow.specialArgs;
sharedModules = [ sharedModules = [
../home ../home

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,8 @@
mkOption mkOption
types types
; ;
in {
options = {
outputs = mkOption { outputs = mkOption {
type = types.submoduleWith { type = types.submoduleWith {
modules = [ modules = [
@ -36,10 +37,10 @@
configuration merging. configuration merging.
''; '';
}; };
in {
options = {
inherit outputs;
}; };
config = {inherit (config) flake;}; config = {
# ensure a minimal version is set
outputs = {};
};
} }

View file

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

View file

@ -12,7 +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, _snowFlake,
lib, lib,
specialArgs, specialArgs,
... ...
@ -39,7 +39,7 @@
config = { config = {
nodes = { 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, lib,
systems, systems,
config,
nodesConfig, nodesConfig,
groups,
groupLibs,
... ...
}: { }: {
options = let options = let
@ -25,6 +26,11 @@
types types
; ;
inherit
(groupLibs)
resolveGroupsInheritance
;
flakeRef = types.either types.str types.path; flakeRef = types.either types.str types.path;
in { in {
enabled = lib.mkOption { enabled = lib.mkOption {
@ -113,8 +119,8 @@
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: # apply = groupsFn:
groupsFn nodesConfig.groups; # groupsFn nodesConfig.groups |> resolveGroupsInheritance;
}; };
deploy = { deploy = {
@ -266,32 +272,32 @@
}; };
}; };
config = let # config = let
throwGotNull = name: # throwGotNull = name:
throw '' # throw ''
[snow] `nodes.<name>.${name}` must be set for all nodes! (got: <null>) # [snow] `nodes.<name>.${name}` must be set for all nodes! (got: <null>)
''; # '';
givenSystem = # givenSystem =
(config.system != null) # (config.system != null)
|| throwGotNull "system"; # || throwGotNull "system";
givenBase = # givenBase =
(config.base != null) # (config.base != null)
|| throwGotNull "base"; # || throwGotNull "base";
givenHomeManager = # givenHomeManager =
(config.homeManager != null) # (config.homeManager != null)
|| throwGotNull "homeManager"; # || throwGotNull "homeManager";
givenDeployHost = # givenDeployHost =
(config.deploy.ssh.host != null) # (config.deploy.ssh.host != null)
|| throwGotNull "deploy.ssh.host"; # || throwGotNull "deploy.ssh.host";
in # in
assert givenSystem # assert givenSystem
&& givenBase # && givenBase
&& givenHomeManager # && givenHomeManager
&& givenDeployHost; { # && givenDeployHost; {
# extend these from the nodes configuration # # extend these from the nodes configuration
inherit (nodesConfig) modules args; # inherit (nodesConfig) modules args;
}; # };
} }

View file

@ -12,7 +12,9 @@
# 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, _snowFlake,
snow,
root,
lib, lib,
config, config,
specialArgs, specialArgs,
@ -26,7 +28,10 @@
flakeRef = types.either types.str types.path; 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 { in {
options = { options = {
base = lib.mkOption { base = lib.mkOption {
@ -94,8 +99,6 @@ in {
description = '' description = ''
Hierarchical groups that nodes can be a member of. Hierarchical groups that nodes can be a member of.
''; '';
apply = groupLibs.parseGroupsDecl;
}; };
nodes = mkOption { nodes = mkOption {
@ -103,7 +106,8 @@ in {
specialArgs = specialArgs =
specialArgs specialArgs
// { // {
nodeConfig = config; nodesConfig = config;
inherit groupLibs;
}; };
modules = [./node.nix]; modules = [./node.nix];
}); });

View file

@ -1,10 +1,10 @@
{ {
config, config,
_snow, _snowFlake,
... ...
}: { }: {
outputs.checks = outputs.checks =
_snow.inputs.deploy-rs.lib _snowFlake.inputs.deploy-rs.lib
|> builtins.mapAttrs (system: deployLib: |> builtins.mapAttrs (system: deployLib:
deployLib.deployChecks config.outputs.deploy); 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, config,
... ...
}: let }: {
inherit outputs.deploy.nodes = snow.lib.mapNodes config.nodes ({
(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 ({
name, name,
node, node,
... ...
@ -49,7 +22,7 @@ in {
confirmTimeout confirmTimeout
; ;
nixosFor = system: _snow.inputs.deploy-rs.lib.${system}.activate.nixos; nixosFor = system: _snowFlake.inputs.deploy-rs.lib.${system}.activate.nixos;
in { in {
hostname = hostname =
if ssh.host != null 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, snow,
config, config,
systems, systems,
@ -27,15 +18,22 @@
nodes nodes
; ;
in { in {
outputs.nixosConfigurations = mapNodes nodes ( outputs.nixosConfigurations = let
groups = snow.lib.parseGroupDecls root config.nodes.groups;
in
snow.lib.mapNodes nodes (
{ {
base, base,
lib, lib,
name, name,
node, node,
groupModules,
... ...
}: let }: let
nodeGroups =
(node.groups groups)
|> snow.lib.resolveGroupsInheritance
|> snow.lib.groupModules;
homeManager = homeManager =
if node.homeManager != null if node.homeManager != null
then node.homeManager then node.homeManager
@ -55,7 +53,8 @@ in {
hostname = name; hostname = name;
_snow = { _snow = {
inherit inputs userArgs snowArgs homeManager; inherit (_snowFlake) inputs;
inherit userArgs snowArgs homeManager;
specialArgs = userArgs // snowArgs; specialArgs = userArgs // snowArgs;
}; };
}; };
@ -74,10 +73,10 @@ in {
inherit specialArgs; inherit specialArgs;
modules = modules =
[ [
snow.nixosModules.default _snowFlake.self.nixosModules.default
(snow.findImport /${root}/hosts/${name}) (snow.lib.findImport /${root}/hosts/${name})
] ]
++ (groupModules root) ++ nodeGroups
++ node.modules ++ node.modules
++ nodes.modules; ++ nodes.modules;
} }

View file

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

View file

@ -14,9 +14,12 @@
minVersion = "23.05pre-git"; minVersion = "23.05pre-git";
isNixpkgsValidVersion = let 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 in
(builtins.compareVersions lib.version minVersion < 0) (builtins.compareVersions lib.version minVersion >= 0)
|| abort '' || abort ''
The nixpkgs dependency of snow 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},

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;
}