Compare commits

..

No commits in common. "f4dca25c28135abd9284cfce5ecfdeda8f0018ae" and "3e29615db9ac072029557b031ee814f820e49791" have entirely different histories.

34 changed files with 1219 additions and 718 deletions

21
flake.lock generated
View file

@ -185,30 +185,9 @@
"microvm": "microvm", "microvm": "microvm",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nt": "nt", "nt": "nt",
"sops-nix": "sops-nix",
"systems": "systems_3" "systems": "systems_3"
} }
}, },
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1773096132,
"narHash": "sha256-M3zEnq9OElB7zqc+mjgPlByPm1O5t2fbUrH3t/Hm5Ag=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "d1ff3b1034d5bab5d7d8086a7803c5a5968cd784",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"spectrum": { "spectrum": {
"flake": false, "flake": false,
"locked": { "locked": {

View file

@ -44,7 +44,7 @@
nt, nt,
... ...
} @ inputs: } @ inputs:
import ./nix import ./cerulean
{ {
inherit inputs self nt; inherit inputs self nt;
inherit (nt) mix; inherit (nt) mix;

View file

@ -18,14 +18,15 @@
} @ args: let } @ args: let
inherit (nt) findImport; inherit (nt) findImport;
in in
mix.newMixture args (mixture: { mix.newMixture args (mixture: let
inherit (mixture) mapNodes;
in {
includes = { includes = {
private = [ private = [
./lib/nodes.nix ./lib/nodes.nix
]; ];
public = [ public = [
./flake ./flake
./lib.nix
]; ];
}; };

View file

@ -4,53 +4,33 @@
systems, systems,
... ...
}: let }: let
inherit
(builtins)
attrNames
concatStringsSep
filter
length
warn
;
inherit (inputs.nixpkgs) lib; inherit (inputs.nixpkgs) lib;
in { in {
# snow.flake # snow.flake
flake = flakeInputs: root: let flake = flakeInputs: root: let
snowflake = lib.evalModules { snowflake = lib.evalModules {
class = "snowflake"; class = "snowflake";
specialArgs = let # XXX: TODO: abort if inputs contains reserved names
reservedSpecialArgs = { specialArgs =
(flakeInputs
// {
inherit (this) snow; inherit (this) snow;
inherit systems root; inherit systems root;
inputs = flakeInputs; inputs = flakeInputs;
}; })
# XXX: TODO:
warnIfReserved = let # |> (x: builtins.removeAttrs x ["self" "nodes"]);
getReservedNames = names: |> (x: builtins.removeAttrs x ["self"]);
reservedSpecialArgs
|> attrNames
|> filter (name: names?${name});
reservedNames =
flakeInputs
|> attrNames
|> getReservedNames;
in
(length reservedNames == 0)
|| warn ''
[snow] Your `flake.nix` declares inputs with reserved names!
[snow] These will be accessible only via `inputs.''${NAME}`
[snow] Please rename the following:
[snow] ${concatStringsSep reservedNames ", "}
''
true;
in
assert warnIfReserved;
flakeInputs // reservedSpecialArgs;
modules = [ modules = [
./module.nix ./module.nix
({config, ...}: {
_module.args = {
self = config;
# XXX: TODO:
# nodes = config.nodes.nodes;
};
})
]; ];
}; };
in in

318
nix/snow/flake/lib.nix.bak Normal file
View file

@ -0,0 +1,318 @@
{ lib
# Optionally a string with extra version info to be included in the error message
# in case is lib is out of date. Empty or starts with space.
, revInfo ? ""
}:
let
inherit (lib)
mkOption
mkOptionType
defaultFunctor
isAttrs
isFunction
showOption
throwIf
types
warnIf
getAttrFromPath
setAttrByPath
attrByPath
optionalAttrs
;
inherit (lib.modules)
mkAliasAndWrapDefsWithPriority;
inherit (lib.types)
path
submoduleWith
;
# Polyfill isFlake until Nix with https://github.com/NixOS/nix/pull/7207 is common
isFlake = maybeFlake:
if maybeFlake ? _type
then maybeFlake._type == "flake"
else maybeFlake ? inputs && maybeFlake ? outputs && maybeFlake ? sourceInfo;
/**
Deprecated for any use except type-merging into `perSystem`.
Use `lib.types.deferredModuleWith` instead, and add `apply = m: [ m ];` if needed.
The deferredModule type was pioneered in flake-parts for the `perSystem` option.
The Nixpkgs version has an improved merge function that returns a single module,
whereas this version returns a list. The flake-parts version was not updated to
match this improvement in Nixpkgs.
# History
This predates `lib.types.deferredModuleWith`, added in Nixpkgs 22.11
(https://github.com/NixOS/nixpkgs/pull/163617).
Documented as deprecated in flake-parts in January 2026.
*/
deferredModuleWith =
attrs@{ staticModules ? [ ] }: mkOptionType {
name = "deferredModule";
description = "module";
check = x: isAttrs x || isFunction x || path.check x;
merge = loc: defs: staticModules ++ map (def: lib.setDefaultModuleLocation "${def.file}, via option ${showOption loc}" def.value) defs;
inherit (submoduleWith { modules = staticModules; })
getSubOptions
getSubModules;
substSubModules = m: deferredModuleWith (attrs // {
staticModules = m;
});
functor = defaultFunctor "deferredModuleWith" // {
type = deferredModuleWith;
payload = {
inherit staticModules;
};
binOp = lhs: rhs: {
staticModules = lhs.staticModules ++ rhs.staticModules;
};
};
};
# Internal: preserves legacy list-merge behavior for perSystem type-merging.
mkLegacyDeferredModuleType =
module:
deferredModuleWith {
staticModules = [ module ];
};
errorExample = ''
For example:
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } { /* module */ };
To avoid an infinite recursion, *DO NOT* pass `self.inputs` and
*DO NOT* pass `inherit (self) inputs`, but pass the output function
arguments as `inputs` like above.
'';
flake-parts-lib = rec {
evalFlakeModule =
args@
{ inputs ? self.inputs
, specialArgs ? { }
# legacy
, self ? inputs.self or (throw ''
When invoking flake-parts, you must pass all the flake output arguments,
and not just `self.inputs`.
${errorExample}
'')
, moduleLocation ? "${self.outPath}/flake.nix"
}:
let
inputsPos = builtins.unsafeGetAttrPos "inputs" args;
errorLocation =
# Best case: user makes it explicit
args.moduleLocation or (
# Slightly worse: Nix does not technically commit to unsafeGetAttrPos semantics
if inputsPos != null
then inputsPos.file
# Slightly worse: self may not be valid when an error occurs
else if args?inputs.self.outPath
then args.inputs.self.outPath + "/flake.nix"
# Fallback
else "<mkFlake argument>"
);
in
throwIf
(!args?self && !args?inputs) ''
When invoking flake-parts, you must pass in the flake output arguments.
${errorExample}
''
warnIf
(!args?inputs) ''
When invoking flake-parts, it is recommended to pass all the flake output
arguments in the `inputs` parameter. If you only pass `self`, it's not
possible to use the `inputs` module argument in the module `imports`.
Please pass the output function arguments. ${errorExample}
''
(module:
lib.evalModules {
specialArgs = {
inherit self flake-parts-lib moduleLocation;
inputs = args.inputs or /* legacy, warned above */ self.inputs;
} // specialArgs;
modules = [ ./all-modules.nix (lib.setDefaultModuleLocation errorLocation module) ];
class = "flake";
}
);
# Function to extract the default flakeModule from
# what may be a flake, returning the argument unmodified
# if it's not a flake.
#
# Useful to map over an 'imports' list to make it less
# verbose in the common case.
defaultModule = maybeFlake:
if isFlake maybeFlake
then maybeFlake.flakeModules.default or maybeFlake
else maybeFlake;
mkFlake = args: module:
let
eval = flake-parts-lib.evalFlakeModule args module;
in
eval.config.flake;
/**
Deprecated. Declare options directly, e.g. `options.foo.bar = mkOption { ... }`,
provided that `foo` is already declared as a submodule option.
In flake-parts, `flake` is declared as a submodule option by the core modules,
so `options.flake.<name>` declarations work directly.
This function wraps option declarations in a submodule, allowing them to
be merged into an existing submodule option. For example, if `foo` is
already declared as a submodule option, using
`options.foo = mkSubmoduleOptions { bar = mkOption {...}; }` would add
`bar` to the `foo` submodule.
# History
This was a workaround for https://github.com/NixOS/nixpkgs/issues/146882,
fixed in Nixpkgs 22.05 by https://github.com/NixOS/nixpkgs/pull/156533.
With the fix, declaring `options.foo.bar` directly works when `foo` is
already a submodule option. Documented as deprecated in flake-parts in January 2026.
*/
mkSubmoduleOptions =
options:
mkOption {
type = types.submoduleWith {
modules = [{ inherit options; }];
};
};
/**
Deprecated. Use mkPerSystemType/mkPerSystemOption for `perSystem` type-merging, or
use Nixpkgs `types.deferredModule` directly, noting the lack of list wrapping;
see `deferredModuleWith` docs.
*/
mkDeferredModuleType = mkLegacyDeferredModuleType;
/**
Given a module, construct an option type suitable for type-merging into `perSystem`'s type.
*/
mkPerSystemType = mkLegacyDeferredModuleType;
/**
Deprecated. Use mkPerSystemOption for `perSystem` type-merging, or
use `mkOption` and Nixpkgs `types.deferredModule` directly, noting the
lack of list wrapping; see `deferredModuleWith` docs.
*/
mkDeferredModuleOption =
module:
mkOption {
type = flake-parts-lib.mkPerSystemType module;
};
/**
Given a module, construct an option declaration suitable for merging into the core `perSystem` module.
*/
mkPerSystemOption = mkDeferredModuleOption;
# Polyfill https://github.com/NixOS/nixpkgs/pull/344216
# Nixpkgs master 2024-12-09, Nixpkgs 25.05
attrsWith = types.attrsWith or ({ elemType, lazy ? false, placeholder ? "name" }:
if lazy then types.attrsOf elemType else types.lazyAttrsOf elemType);
# Helper function for defining a per-system option that
# gets transposed by the usual flake system logic to a
# top-level flake attribute.
mkTransposedPerSystemModule = { name, option, file }: {
_file = file;
options = {
flake.${name} = mkOption {
type = attrsWith {
elemType = option.type;
lazy = true;
placeholder = "system";
};
default = { };
description = ''
See {option}`perSystem.${name}` for description and examples.
'';
};
perSystem = flake-parts-lib.mkPerSystemOption {
_file = file;
options.${name} = option;
};
};
config = {
transposition.${name} = { };
};
};
# Needed pending https://github.com/NixOS/nixpkgs/pull/198450
mkAliasOptionModule = from: to: { config, options, ... }:
let
fromOpt = getAttrFromPath from options;
toOf = attrByPath to
(abort "Renaming error: option `${showOption to}' does not exist.");
toType = let opt = attrByPath to { } options; in opt.type or (types.submodule { });
in
{
options = setAttrByPath from (mkOption
{
visible = true;
description = "Alias of {option}`${showOption to}`.";
apply = x: (toOf config);
} // optionalAttrs (toType != null) {
type = toType;
});
config = mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt;
};
# Helper function for importing while preserving module location. To be added
# in nixpkgs: https://github.com/NixOS/nixpkgs/pull/230588
# I expect these functions to remain identical. This one will stick around
# for a while to support older nixpkgs-lib.
importApply =
modulePath: staticArgs:
lib.setDefaultModuleLocation modulePath (import modulePath staticArgs);
inherit (import ./lib/memoize/memoize.nix {
inherit lib;
}) memoizeStr;
/**
`importAndPublish name module` returns a module that both imports the `module`, and exposes it as flake attribute `modules.flake.${name}`.
This also imports the optional [`modules`](https://flake.parts/options/flake-parts-modules.html) module to support that.
*/
importAndPublish = name: module: { lib, ... }: {
_class = "flake";
imports = [
module
./extras/modules.nix
];
flake.modules.flake.${name} = module;
};
};
# A best effort, lenient estimate. Please use a recent nixpkgs lib if you
# override it at all.
minVersion = "23.05pre-git";
in
if builtins.compareVersions lib.version minVersion < 0
then
abort ''
The nixpkgs-lib dependency of flake-parts was overridden but is too old.
The minimum supported version of nixpkgs-lib is ${minVersion},
but the actual version is ${lib.version}${revInfo}.
''
else
flake-parts-lib

View file

@ -18,7 +18,6 @@
}: { }: {
imports = [ imports = [
./nodes ./nodes
./modules
(snow.findImport /${root}/snow) (snow.findImport /${root}/snow)
]; ];
} }

View file

@ -1,3 +0,0 @@
# Snow Module Backend
This source code was tedious so it's just a modified version of the module backend of
[github:hercules-ci/flake-parts](https://github.com/hercules-ci/flake-parts/tree/main/modules).

View file

@ -1,31 +1,23 @@
{ { lib, flake-parts-lib, ... }:
lib, let
snow, inherit (lib)
...
}: let
inherit
(lib)
mkOption mkOption
types types
; ;
inherit (flake-parts-lib)
inherit mkTransposedPerSystemModule
(snow)
mkPerSystemFlakeOutput
; ;
derivationType = programType = lib.types.coercedTo derivationType lib.getExe lib.types.str;
lib.types.package
// { derivationType = lib.types.package // {
check = lib.isDerivation; check = lib.isDerivation;
}; };
programType = lib.types.coercedTo derivationType lib.getExe lib.types.str;
appType = lib.types.submodule { appType = lib.types.submodule {
options = { options = {
type = mkOption { type = mkOption {
type = lib.types.enum ["app"]; type = lib.types.enum [ "app" ];
default = "app"; default = "app";
description = '' description = ''
A type tag for `apps` consumers. A type tag for `apps` consumers.
@ -39,7 +31,7 @@
}; };
meta = mkOption { meta = mkOption {
type = types.lazyAttrsOf lib.types.raw; type = types.lazyAttrsOf lib.types.raw;
default = {}; default = { };
# TODO refer to Nix manual 2.25 # TODO refer to Nix manual 2.25
description = '' description = ''
Metadata information about the app. Metadata information about the app.
@ -51,11 +43,11 @@
}; };
}; };
in in
mkPerSystemFlakeOutput { mkTransposedPerSystemModule {
name = "apps"; name = "apps";
option = mkOption { option = mkOption {
type = types.lazyAttrsOf appType; type = types.lazyAttrsOf appType;
default = {}; default = { };
description = '' description = ''
Programs runnable with nix run `<name>`. Programs runnable with nix run `<name>`.
''; '';
@ -66,4 +58,4 @@ in
''; '';
}; };
file = ./apps.nix; file = ./apps.nix;
} }

View file

@ -1,26 +1,21 @@
{ { lib, flake-parts-lib, ... }:
lib, let
snow, inherit (lib)
...
}: let
inherit
(lib)
mkOption mkOption
types types
; ;
inherit inherit (flake-parts-lib)
(snow) mkTransposedPerSystemModule
mkPerSystemFlakeOutput
; ;
in in
mkPerSystemFlakeOutput { mkTransposedPerSystemModule {
name = "checks"; name = "checks";
option = mkOption { option = mkOption {
type = types.lazyAttrsOf types.package; type = types.lazyAttrsOf types.package;
default = {}; default = { };
description = '' description = ''
Derivations to be built by [`nix flake check`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake-check.html). Derivations to be built by [`nix flake check`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake-check.html).
''; '';
}; };
file = ./checks.nix; file = ./checks.nix;
} }

View file

@ -0,0 +1,78 @@
{ config, flake-parts-lib, lib, options, getSystem, extendModules, ... }:
let
inherit (lib)
mapAttrs
mkIf
mkOption
optionalAttrs
types
;
inherit (flake-parts-lib)
mkPerSystemOption
;
mkDebugConfig = { config, options, extendModules }: config // {
inherit config;
inherit (config) _module;
inherit options;
inherit extendModules;
};
in
{
options = {
debug = mkOption {
type = types.bool;
default = false;
description = ''
Whether to add the attributes `debug`, `allSystems` and `currentSystem`
to the flake output. When `true`, this allows inspection of options via
`nix repl`.
```
$ nix repl
nix-repl> :lf .
nix-repl> currentSystem._module.args.pkgs.hello
«derivation /nix/store/7vf0d0j7majv1ch1xymdylyql80cn5fp-hello-2.12.1.drv»
```
Each of `debug`, `allSystems.<system>` and `currentSystem` is an
attribute set consisting of the `config` attributes, plus the extra
attributes `_module`, `config`, `options`, `extendModules`. So note that
these are not part of the `config` parameter, but are merged in for
debugging convenience.
- `debug`: The top-level options
- `allSystems`: The `perSystem` submodule applied to the configured `systems`.
- `currentSystem`: Shortcut into `allSystems`. Only available in impure mode.
Works for arbitrary system values.
See [Expore and debug option values](../debug.html) for more examples.
'';
};
perSystem = mkPerSystemOption
({ options, config, extendModules, ... }: {
_file = ./formatter.nix;
options = {
debug = mkOption {
description = ''
Values to return in e.g. `allSystems.<system>` when
[`debug = true`](#opt-debug).
'';
type = types.lazyAttrsOf types.raw;
};
};
config = {
debug = mkDebugConfig { inherit config options extendModules; };
};
});
};
config = mkIf config.debug {
flake = {
debug = mkDebugConfig { inherit config options extendModules; };
allSystems = mapAttrs (_s: c: c.debug) config.allSystems;
} // optionalAttrs (builtins?currentSystem) {
currentSystem = (getSystem builtins.currentSystem).debug;
};
};
}

View file

@ -1,15 +0,0 @@
{...}: {
imports = [
./outputs.nix
./apps.nix
./checks.nix
./devShells.nix
./formatter.nix
./legacyPackages.nix
./nixosConfigurations.nix
./nixosModules.nix
./overlays.nix
./packages.nix
];
}

View file

@ -1,24 +1,19 @@
{ { lib, flake-parts-lib, ... }:
lib, let
snow, inherit (lib)
...
}: let
inherit
(lib)
mkOption mkOption
types types
literalExpression literalExpression
; ;
inherit inherit (flake-parts-lib)
(snow) mkTransposedPerSystemModule
mkPerSystemFlakeOutput
; ;
in in
mkPerSystemFlakeOutput { mkTransposedPerSystemModule {
name = "devShells"; name = "devShells";
option = mkOption { option = mkOption {
type = types.lazyAttrsOf types.package; type = types.lazyAttrsOf types.package;
default = {}; default = { };
description = '' description = ''
An attribute set of packages to be used as shells. An attribute set of packages to be used as shells.
[`nix develop .#<name>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-develop.html) will run `devShells.<name>`. [`nix develop .#<name>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-develop.html) will run `devShells.<name>`.
@ -32,4 +27,4 @@ in
''; '';
}; };
file = ./devShells.nix; file = ./devShells.nix;
} }

View file

@ -9,7 +9,7 @@
types types
; ;
outputs = mkOption { flake = mkOption {
type = types.submoduleWith { type = types.submoduleWith {
modules = [ modules = [
{ {
@ -21,6 +21,7 @@
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
@ -38,7 +39,8 @@
}; };
in { in {
options = { options = {
inherit outputs; inherit flake;
output = {inherit flake;};
}; };
config = {inherit (config) flake;}; config = {inherit (config) flake;};

View file

@ -1,26 +1,52 @@
{ { config, lib, flake-parts-lib, ... }:
lib, let
snow, inherit (lib)
... filterAttrs
}: let mapAttrs
inherit
(lib)
mkOption mkOption
optionalAttrs
types types
; ;
inherit inherit (flake-parts-lib)
(snow) mkPerSystemOption
mkPerSystemFlakeOutput
; ;
in in
mkPerSystemFlakeOutput { {
name = "formatter"; options = {
option = mkOption { flake.formatter = mkOption {
type = types.lazyAttrsOf types.package;
default = { };
description = ''
An attribute set of per system a package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
'';
};
perSystem = mkPerSystemOption {
_file = ./formatter.nix;
options = {
formatter = mkOption {
type = types.nullOr types.package; type = types.nullOr types.package;
default = null; default = null;
description = '' description = ''
A package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html). A package used by [`nix fmt`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-fmt.html).
''; '';
}; };
file = ./apps.nix; };
} };
};
config = {
flake.formatter =
mapAttrs
(k: v: v.formatter)
(filterAttrs
(k: v: v.formatter != null)
config.allSystems
);
perInput = system: flake:
optionalAttrs (flake?formatter.${system}) {
formatter = flake.formatter.${system};
};
};
}

View file

@ -1,26 +1,21 @@
{ { lib, flake-parts-lib, ... }:
lib, let
snow, inherit (lib)
...
}: let
inherit
(lib)
mkOption mkOption
types types
; ;
inherit inherit (flake-parts-lib)
(snow) mkTransposedPerSystemModule
mkPerSystemFlakeOutput
; ;
in in
mkPerSystemFlakeOutput { mkTransposedPerSystemModule {
name = "legacyPackages"; name = "legacyPackages";
option = mkOption { option = mkOption {
type = types.lazyAttrsOf types.raw; type = types.lazyAttrsOf types.raw;
default = {}; default = { };
description = '' description = ''
Used for nixpkgs packages, also accessible via `nix build .#<name>` [`nix build .#<name>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html). An attribute set of unmergeable values. This is also used by [`nix build .#<attrpath>`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html).
''; '';
}; };
file = ./legacyPackages.nix; file = ./legacyPackages.nix;
} }

View file

@ -0,0 +1,32 @@
{ withSystem, ... }:
{
config = {
_module.args = {
moduleWithSystem =
module:
{ config, ... }:
let
system =
config._module.args.system or
config._module.args.pkgs.stdenv.hostPlatform.system or
(throw "moduleWithSystem: Could not determine the configuration's system parameter for this module system application.");
allArgs = withSystem system (args: args);
lazyArgsPerParameter = f: builtins.mapAttrs
(k: v: allArgs.${k} or (throw "moduleWithSystem: module argument `${k}` does not exist."))
(builtins.functionArgs f);
# Use reflection to make the call lazy in the argument.
# Restricts args to the ones declared.
callLazily = f: a: f (lazyArgsPerParameter f);
in
{
imports = [
(callLazily module allArgs)
];
};
};
};
}

View file

@ -1,15 +1,16 @@
{lib, ...}: let { lib, ... }:
inherit let
(lib) inherit (lib)
mkOption mkOption
types types
literalExpression literalExpression
; ;
in { in
{
options = { options = {
outputs.nixosConfigurations = mkOption { flake.nixosConfigurations = mkOption {
type = types.lazyAttrsOf types.raw; type = types.lazyAttrsOf types.raw;
default = {}; default = { };
description = '' description = ''
Instantiated NixOS configurations. Used by `nixos-rebuild`. Instantiated NixOS configurations. Used by `nixos-rebuild`.

View file

@ -1,23 +1,20 @@
{ { self, lib, moduleLocation, ... }:
lib, let
moduleLocation, inherit (lib)
...
}: let
inherit
(lib)
mapAttrs mapAttrs
mkOption mkOption
types types
; ;
in { in
{
options = { options = {
outputs.nixosModules = mkOption { flake.nixosModules = mkOption {
type = types.lazyAttrsOf types.deferredModule; type = types.lazyAttrsOf types.deferredModule;
default = {}; default = { };
apply = mapAttrs (k: v: { apply = mapAttrs (k: v: {
_class = "nixos"; _class = "nixos";
_file = "${toString moduleLocation}#nixosModules.${k}"; _file = "${toString moduleLocation}#nixosModules.${k}";
imports = [v]; imports = [ v ];
}); });
description = '' description = ''
NixOS modules. NixOS modules.

View file

@ -0,0 +1,26 @@
#
# Nixpkgs module. The only exception to the rule.
#
# Provides a `pkgs` argument in `perSystem`.
#
# Arguably, this shouldn't be in flake-parts, but in nixpkgs.
# Nixpkgs could define its own module that does this, which would be
# a more consistent UX, but for now this will do.
#
# The existence of this module does not mean that other flakes' logic
# will be accepted into flake-parts, because it's against the
# spirit of Flakes.
#
{
config = {
perSystem = { inputs', lib, ... }: {
config = {
_module.args.pkgs = lib.mkOptionDefault (
builtins.seq
(inputs'.nixpkgs or (throw "flake-parts: The flake does not have a `nixpkgs` input. Please add it, or set `perSystem._module.args.pkgs` yourself."))
inputs'.nixpkgs.legacyPackages
);
};
};
};
}

View file

@ -1,18 +1,19 @@
{lib, ...}: let { lib, ... }:
inherit let
(lib) inherit (lib)
mkOption mkOption
types types
; ;
in { in
{
options = { options = {
outputs.overlays = mkOption { flake.overlays = mkOption {
# uniq -> ordered: https://github.com/NixOS/nixpkgs/issues/147052 # uniq -> ordered: https://github.com/NixOS/nixpkgs/issues/147052
# also update description when done # also update description when done
type = types.lazyAttrsOf (types.uniq (types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified)))); type = types.lazyAttrsOf (types.uniq (types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified))));
# This eta expansion exists for the sole purpose of making nix flake check happy. # This eta expansion exists for the sole purpose of making nix flake check happy.
apply = lib.mapAttrs (_k: f: final: prev: f final prev); apply = lib.mapAttrs (_k: f: final: prev: f final prev);
default = {}; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
default = final: prev: {}; default = final: prev: {};

View file

@ -1,24 +1,18 @@
{ { lib, flake-parts-lib, ... }:
lib, let
snow, inherit (lib)
...
}: let
inherit
(lib)
mkOption mkOption
types types
; ;
inherit (flake-parts-lib)
inherit mkTransposedPerSystemModule
(snow)
mkPerSystemFlakeOutput
; ;
in in
mkPerSystemFlakeOutput { mkTransposedPerSystemModule {
name = "packages"; name = "packages";
option = mkOption { option = mkOption {
type = types.lazyAttrsOf types.package; type = types.lazyAttrsOf types.package;
default = {}; default = { };
description = '' description = ''
An attribute set of packages to be built by [`nix build`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html). An attribute set of packages to be built by [`nix build`](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-build.html).
@ -26,4 +20,4 @@ in
''; '';
}; };
file = ./packages.nix; file = ./packages.nix;
} }

View file

@ -0,0 +1,159 @@
{ config, lib, flake-parts-lib, self, ... }:
let
inherit (lib)
genAttrs
mapAttrs
mkOption
types
;
inherit (lib.strings)
escapeNixIdentifier
;
inherit (flake-parts-lib)
mkPerSystemType
;
rootConfig = config;
# Stubs for self and inputs. While it'd be possible to define aliases
# inside perSystem, that is not a general solution, and it would make
# top.config harder to discover, stretching the learning curve rather
# than flattening it.
throwAliasError' = param:
throw ''
`${param}` (without `'`) is not a `perSystem` module argument, but a
module argument of the top level config.
The following is an example usage of `${param}`. Note that its binding
is in the `top` parameter list, which is declared by the top level module
rather than the `perSystem` module.
top@{ config, lib, ${param}, ... }: {
perSystem = { config, ${param}', ... }: {
# in scope here:
# - ${param}
# - ${param}'
# - config (of perSystem)
# - top.config (note the `top@` pattern)
};
}
'';
throwAliasError = param:
throw ''
`${param}` is not a `perSystem` module argument, but a module argument of
the top level config.
The following is an example usage of `${param}`. Note that its binding
is in the `top` parameter list, which is declared by the top level module
rather than the `perSystem` module.
top@{ config, lib, ${param}, ... }: {
perSystem = { config, ... }: {
# in scope here:
# - ${param}
# - config (of perSystem)
# - top.config (note the `top@` pattern)
};
}
'';
/**
We primarily use `systems` to help memoize the per system context, but that
doesn't extend to arbitrary `system`s.
For that, we use the slightly less efficient, but perfectly acceptable
`memoizeStr` function.
*/
otherMemoizedSystems = flake-parts-lib.memoizeStr config.perSystem;
in
{
options = {
systems = mkOption {
description = ''
All the system types to enumerate in the flake output subattributes.
In other words, all valid values for `system` in e.g. `packages.<system>.foo`.
'';
type = types.listOf types.str;
};
perInput = mkOption {
description = ''
A function that pre-processes flake inputs.
It is called for users of `perSystem` such that `inputs'.''${name} = config.perInput system inputs.''${name}`.
This is used for [`inputs'`](../module-arguments.html#inputs) and [`self'`](../module-arguments.html#self).
The attributes returned by the `perInput` function definitions are merged into a single namespace (per input),
so each module should return an attribute set with usually only one or two predictable attribute names. Otherwise,
the `inputs'` namespace gets polluted.
'';
type = types.functionTo (types.functionTo (types.lazyAttrsOf types.unspecified));
};
perSystem = mkOption {
description = ''
A function from system to flake-like attributes omitting the `<system>` attribute.
Modules defined here have access to the suboptions and [some convenient module arguments](../module-arguments.html).
'';
type = mkPerSystemType ({ config, system, ... }: {
_file = ./perSystem.nix;
config = {
_module.args.inputs' =
mapAttrs
(inputName: input:
builtins.addErrorContext "while retrieving system-dependent attributes for input ${escapeNixIdentifier inputName}" (
if input._type or null == "flake"
then rootConfig.perInput system input
else
throw "Trying to retrieve system-dependent attributes for input ${escapeNixIdentifier inputName}, but this input is not a flake. Perhaps flake = false was added to the input declarations by mistake, or you meant to use a different input, or you meant to use plain old inputs, not inputs'."
)
)
self.inputs;
_module.args.self' =
builtins.addErrorContext "while retrieving system-dependent attributes for a flake's own outputs" (
rootConfig.perInput system self
);
# Custom error messages
_module.args.self = throwAliasError' "self";
_module.args.inputs = throwAliasError' "inputs";
_module.args.getSystem = throwAliasError "getSystem";
_module.args.withSystem = throwAliasError "withSystem";
_module.args.moduleWithSystem = throwAliasError "moduleWithSystem";
};
});
apply = modules: system:
(lib.evalModules {
inherit modules;
prefix = [ "perSystem" system ];
specialArgs = {
inherit system;
};
class = "perSystem";
}).config;
};
allSystems = mkOption {
type = types.lazyAttrsOf types.unspecified;
description = "The system-specific config for each of systems.";
internal = true;
};
};
config = {
allSystems = genAttrs config.systems config.perSystem;
_module.args.getSystem = system: config.allSystems.${system} or (otherMemoizedSystems system);
# The warning is there for a reason. Only use this in situations where the
# performance cost has already been incurred, such as in `flakeModules.easyOverlay`,
# where we run in the context of an overlay, and the performance cost of the
# extra `pkgs` makes the cost of running `perSystem` probably negligible.
_module.args.getSystemIgnoreWarning = system: config.allSystems.${system} or (config.perSystem system);
};
}

View file

@ -0,0 +1,132 @@
{ config, lib, flake-parts-lib, ... }:
let
inherit (lib)
filterAttrs
mapAttrs
mkOption
types
;
inherit (lib.strings)
escapeNixIdentifier
;
transpositionModule = {
options = {
adHoc = mkOption {
type = types.bool;
default = false;
description = ''
Whether to provide a stub option declaration for {option}`perSystem.<name>`.
The stub option declaration does not support merging and lacks
documentation, so you are recommended to declare the {option}`perSystem.<name>`
option yourself and avoid {option}`adHoc`.
'';
};
};
};
perInputAttributeError = { flake, attrName, system, attrConfig }:
# This uses flake.outPath for lack of a better identifier.
# Consider adding a perInput variation that has a normally-redundant argument for the input name.
# Tested manually with
# perSystem = { inputs', ... }: {
# packages.extra = inputs'.nixpkgs.extra;
# packages.default = inputs'.nixpkgs.packages.default;
# packages.veryWrong = (top.config.perInput "x86_64-linux" inputs'.nixpkgs.legacyPackages.hello).packages.default;
# };
# transposition.extra = {};
let
attrPath = "${escapeNixIdentifier attrName}.${escapeNixIdentifier system}";
flakeIdentifier =
if flake._type or null != "flake"
then
throw "An attempt was made to access attribute ${attrPath} on a value that's supposed to be a flake, but may not be a proper flake."
else
builtins.addErrorContext "while trying to find out how to describe what is supposedly a flake, whose attribute ${attrPath} was accessed but does not exist" (
toString flake.outPath
);
# This ought to be generalized by extending attrConfig, but this is the only known and common mistake for now.
alternateAttrNameHint =
if attrName == "packages" && flake?legacyPackages
then # Unfortunately we can't just switch them out, because that will put packages *sets* where single packages are expected in user code, resulting in potentially much worse and more confusing errors down the line.
"\nIt does define legacyPackages; try that instead?"
else "";
in
if flake?${attrName}
then
throw ''
Attempt to access ${attrPath} of flake ${flakeIdentifier}, but it does not have it.
It does have attribute ${escapeNixIdentifier attrName}, so it appears that it does not support system type ${escapeNixIdentifier system}.
''
else
throw ''
Attempt to access ${attrPath} of flake ${flakeIdentifier}, but it does not have attribute ${escapeNixIdentifier attrName}.${alternateAttrNameHint}
'';
in
{
options = {
transposition = lib.mkOption {
description = ''
A helper that defines transposed attributes in the flake outputs.
When you define `transposition.foo = { };`, definitions are added to the effect of (pseudo-code):
```nix
flake.foo.''${system} = (perSystem system).foo;
perInput = system: inputFlake: inputFlake.foo.''${system};
```
Transposition is the operation that swaps the indices of a data structure.
Here it refers specifically to the transposition between
```plain
perSystem: .''${system}.''${attribute}
outputs: .''${attribute}.''${system}
```
It also defines the reverse operation in [{option}`perInput`](#opt-perInput).
'';
type =
types.lazyAttrsOf
(types.submoduleWith { modules = [ transpositionModule ]; });
};
};
config = {
flake =
lib.mapAttrs
(attrName: attrConfig:
mapAttrs
(system: v: v.${attrName} or (
abort ''
Could not find option ${attrName} in the perSystem module. It is required to declare such an option whenever transposition.<name> is defined (and in this instance <name> is ${attrName}).
''))
config.allSystems
)
config.transposition;
perInput =
system: flake:
mapAttrs
(attrName: attrConfig:
flake.${attrName}.${system} or (
throw (perInputAttributeError { inherit system flake attrName attrConfig; })
)
)
config.transposition;
perSystem = {
options =
mapAttrs
(k: v: lib.mkOption { })
(filterAttrs
(k: v: v.adHoc)
config.transposition
);
};
};
}

View file

@ -0,0 +1,37 @@
{ lib, flake-parts-lib, getSystem, ... }:
let
inherit (lib)
mkOption
types
;
inherit (flake-parts-lib)
mkPerSystemOption
;
in
{
options = {
perSystem = mkPerSystemOption ({ config, options, specialArgs, ... }: {
_file = ./perSystem.nix;
options = {
allModuleArgs = mkOption {
type = types.lazyAttrsOf (types.raw or types.unspecified);
internal = true;
readOnly = true;
description = "Internal option that exposes _module.args, for use by withSystem.";
};
};
config = {
allModuleArgs = config._module.args // specialArgs // { inherit config options; };
};
});
};
config = {
_module.args = {
withSystem =
system: f:
f
(getSystem system).allModuleArgs;
};
};
}

View file

@ -12,7 +12,6 @@
# 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,
... ...
@ -26,20 +25,38 @@
in in
mkOption { mkOption {
description = '' description = ''
Snowflake node declarations. Cerulean 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;
};
};
} }

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

@ -11,13 +11,7 @@
# 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
@ -25,8 +19,6 @@
; ;
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 {
@ -57,18 +49,6 @@ 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 = [];
@ -87,28 +67,15 @@ in {
''; '';
}; };
groups = mkOption { homeManager = mkOption {
type = types.attrs; type = types.nullOr flakeRef;
default = {}; default = null;
example = lib.literalExpression "{ servers = { staging = {}; production = {}; }; }"; example = lib.literalExpression "inputs.home-manager";
description = '' description = ''
Hierarchical groups that nodes can be a member of. 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)
apply = groupLibs.parseGroupsDecl; 2. `options.nodes.nodes.<name>.homeManager` (takes prescedence over `options.nodes.homeManager`)
};
nodes = mkOption {
type = types.attrsOf (types.submoduleWith {
specialArgs =
specialArgs
// {
nodeConfig = config;
};
modules = [./node.nix];
});
description = ''
Node (host systems) declarations.
''; '';
}; };
}; };

View file

@ -14,18 +14,16 @@
{ {
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;
@ -45,65 +43,6 @@
''; '';
}; };
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);
@ -112,9 +51,6 @@
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 = {
@ -155,7 +91,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 Snowflake's recommended auth methods instead. NOT RECOMMENDED. Use one of Cerulean's recommended auth methods instead.
''; '';
}; };
@ -228,7 +164,7 @@
user = mkOption { user = mkOption {
type = types.str; type = types.str;
default = "snowbld"; default = "cerubld";
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.
@ -247,7 +183,7 @@
publicKeys = mkOption { publicKeys = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = []; default = [];
example = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIeyZuUUmyUYrYaEJwEMvcXqZFYm1NaZab8klOyK6Imr me@myputer"]; example = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIeyZuUUmyUYrYaEJwEMvcXqZFYm1NaZab8klOyK6Imr me@puter"];
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.
@ -265,33 +201,4 @@
}; };
}; };
}; };
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;
};
} }

View file

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

View file

@ -1,37 +1,4 @@
{ deploy.nodes = mapNodes nodes ({
_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 ({
name, name,
node, node,
... ...
@ -49,7 +16,7 @@ in {
confirmTimeout confirmTimeout
; ;
nixosFor = system: _snow.inputs.deploy-rs.lib.${system}.activate.nixos; nixosFor = system: inputs.deploy-rs.lib.${system}.activate.nixos;
in { in {
hostname = hostname =
if ssh.host != null if ssh.host != null
@ -58,7 +25,7 @@ in {
profilesOrder = ["default"]; # profiles priority profilesOrder = ["default"]; # profiles priority
profiles.default = { profiles.default = {
path = nixosFor node.system config.outputs.nixosConfigurations.${name}; path = nixosFor node.system nixosConfigurations.${name};
user = user; user = user;
sudo = "sudo -u"; sudo = "sudo -u";
@ -76,15 +43,15 @@ in {
sshOpts = sshOpts =
ssh.opts ssh.opts
++ ( ++ (
if builtins.elem "-p" ssh.opts if elem "-p" ssh.opts
then [] then []
else ["-p" (toString ssh.port)] else ["-p" (toString ssh.port)]
) )
++ ( ++ (
if builtins.elem "-A" ssh.opts if elem "-A" ssh.opts
then [] then []
else ["-A"] else ["-A"]
); );
}; };
}); });
}

View file

@ -8,26 +8,7 @@
# options = { ... }; # options = { ... };
# type = { ... }; # type = { ... };
# } # }
{ nixosConfigurations = mapNodes nodes (
snow,
config,
systems,
root,
...
}: let
inherit
(builtins)
all
attrNames
warn
;
inherit
(config)
nodes
;
in {
outputs.nixosConfigurations = mapNodes nodes (
{ {
base, base,
lib, lib,
@ -49,37 +30,38 @@ in {
null; null;
userArgs = nodes.args // node.args; userArgs = nodes.args // node.args;
snowArgs = { ceruleanArgs = {
inherit systems snow root base nodes node; inherit systems root base nodes node;
inherit (node) system; inherit (node) system;
inherit (this) snow;
hostname = name; hostname = name;
_snow = { _cerulean = {
inherit inputs userArgs snowArgs homeManager; inherit inputs userArgs ceruleanArgs homeManager;
specialArgs = userArgs // snowArgs; specialArgs = userArgs // ceruleanArgs;
}; };
}; };
specialArgs = assert (userArgs specialArgs = assert (userArgs
|> attrNames |> attrNames
|> all (argName: |> all (argName:
! snowArgs ? argName ! ceruleanArgs ? argName
|| abort '' || abort ''
`specialArgs` are like super important to Snow my love... </3 `specialArgs` are like super important to Cerulean my love... </3
But `args.${argName}` is a reserved argument name :( But `args.${argName}` is a reserved argument name :(
'')); ''));
snowArgs._snow.specialArgs; ceruleanArgs._cerulean.specialArgs;
in in
lib.nixosSystem { lib.nixosSystem {
inherit (node) system; inherit (node) system;
inherit specialArgs; inherit specialArgs;
modules = modules =
[ [
snow.nixosModules.default self.nixosModules.default
(snow.findImport /${root}/hosts/${name}) (this.findImport /${root}/hosts/${name})
] ]
++ (groupModules root) ++ (groupModules root)
++ node.modules ++ node.modules
++ nodes.modules; ++ nodes.modules;
} }
); );
}

View file

@ -1,27 +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,
mix,
...
} @ args: let
inherit (nt) findImport;
in
mix.newMixture args (mixture: {
includes.public = [
./nixpkgs.nix
];
inherit findImport;
})

View file

@ -1,51 +0,0 @@
{
inputs,
lib,
...
}: let
inherit
(lib)
mkOption
types
;
# A best effort, lenient estimate. Please use a recent nixpkgs lib if you
# override it at all.
minVersion = "23.05pre-git";
isNixpkgsValidVersion = let
revInfo = lib.optional (inputs.nixpkgs?rev) " (nixpkgs-lib.rev: ${inputs.nixpkgs.rev})";
in
(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},
but the actual version is ${lib.version}${revInfo}.
'';
in
assert isNixpkgsValidVersion; {
# Helper function for defining a per-system option that
# gets transposed by the usual flake system logic to a
# top-level outputs attribute.
mkPerSystemFlakeOutput = {
name,
option,
file,
}: {
_file = file;
options = {
outputs.${name} = mkOption {
type = types.attrsWith {
elemType = option.type;
lazy = true;
placeholder = "system";
};
default = {};
description = ''
See {option}`perSystem.${name}` for description and examples.
'';
};
};
};
}

96
nix/snow/lib/nodes.nix Normal file
View 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;
});
}