Initial commit

This commit is contained in:
kirbara 2025-12-01 13:23:24 +07:00
commit cfcc57a8bd
Signed by: exp
GPG key ID: D7E63AD0019E75D9
353 changed files with 18756 additions and 0 deletions

13
flake/den/checkmate.nix Normal file
View file

@ -0,0 +1,13 @@
{ lib, ... }:
{
imports =
let
files = builtins.readDir ./checkmate/tests;
names = builtins.attrNames files;
nixes = builtins.filter (lib.hasSuffix ".nix") names;
tests = map (file: "${./checkmate/tests}/${file}") nixes;
in
tests;
perSystem.treefmt.settings.global.excludes = [ ".github/*/*.md" ];
}

1
flake/den/checkmate/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
flake.lock

View file

@ -0,0 +1,7 @@
{
inputs.target.url = "path:..";
inputs.checkmate.url = "github:vic/checkmate";
inputs.checkmate.inputs.target.follows = "target";
inputs.flake-parts.url = "github:hercules-ci/flake-parts";
outputs = inputs: inputs.checkmate.lib.newFlake;
}

View file

@ -0,0 +1,198 @@
{
lib,
inputs,
config,
...
}:
let
den.lib = inputs.target.lib { inherit lib inputs config; };
inherit (den.lib) parametric canTake;
aspect-example = {
__functor = parametric.atLeast;
nixos.foo = 99;
includes = [
{ nixos.static = 100; }
(
{ host, ... }:
{
nixos.host = host;
}
)
(
{ host, user, ... }:
{
nixos.host-user = [
host
user
];
}
)
(
{
OS,
user,
host,
...
}:
{
nixos.os-user-host = [
OS
user
host
];
}
)
(
{ user, ... }:
{
nixos.user = user;
}
)
(
{ user, ... }@ctx:
if canTake.exactly ctx ({ user }: user) then
{
nixos.user-only = user;
}
else
{ nixos.user-only = false; }
)
(
{ home, ... }:
{
nixos.home = home;
}
)
(_any: {
nixos.any = 10;
})
];
};
flake.tests."test functor applied with empty attrs" = {
expr = (aspect-example { });
expected = {
includes = [
{ nixos.any = 10; }
];
};
};
flake.tests."test functor applied with host only" = {
expr = (
aspect-example {
host = 2;
}
);
expected = {
includes = [
{ nixos.host = 2; } # host
{ nixos.any = 10; }
];
};
};
flake.tests."test functor applied with home only" = {
expr = (
aspect-example {
home = 2;
}
);
expected = {
includes = [
{ nixos.home = 2; } # home
{ nixos.any = 10; }
];
};
};
flake.tests."test functor applied with home and unknown" = {
expr = (
aspect-example {
home = 2;
unknown = 1;
}
);
expected = {
includes = [
{ nixos.home = 2; }
{ nixos.any = 10; }
];
};
};
flake.tests."test functor applied with user only" = {
expr = (
aspect-example {
user = 2;
}
);
expected = {
includes = [
{ nixos.user = 2; } # user
{ nixos.user-only = 2; } # user-only
{ nixos.any = 10; }
];
};
};
flake.tests."test functor applied with user and host" = {
expr = (
aspect-example {
user = 2;
host = 1;
}
);
expected = {
includes = [
{ nixos.host = 1; }
{
nixos.host-user = [
1
2
];
}
{ nixos.user = 2; }
{ nixos.user-only = false; }
{ nixos.any = 10; }
];
};
};
flake.tests."test functor applied with host/user/OS" = {
expr = (
aspect-example {
OS = 0;
user = 2;
host = 1;
}
);
expected = {
includes = [
{ nixos.host = 1; }
{
nixos.host-user = [
1
2
];
}
{
nixos.os-user-host = [
0
2
1
];
}
{ nixos.user = 2; }
{ nixos.user-only = false; }
{ nixos.any = 10; }
];
};
};
in
{
inherit flake;
}

View file

@ -0,0 +1,40 @@
{ ... }@top:
let
lib = top.inputs.nixpkgs.lib;
# deadnix: skip
__findFile =
if true then
import "${top.inputs.target}/nix/den-brackets.nix" { inherit lib config inputs; }
else
__findFile;
inputs = {
};
config.den = {
default.foo = 1;
provides.foo.a = 2;
provides.foo.provides.bar.b = 3;
provides.foo.provides.c = 4;
d = 5;
aspects.foo.a = 6;
aspects.foo.provides.bar.b = 7;
aspects.foo.provides.c = 8;
};
in
{
flake.tests."<den.default>" =
let
expr = <den.default>;
expected.foo = 2;
in
{
inherit expr expected;
};
}

View file

@ -0,0 +1,85 @@
{
lib,
inputs,
config,
...
}:
let
den.lib = inputs.target.lib { inherit inputs lib config; };
takes = den.lib.canTake;
flake.tests."test exactly fails" = {
expr = takes.exactly {
a = 1;
b = 2;
} ({ a }: a);
expected = false;
};
flake.tests."test exactly succeeds" = {
expr = takes.exactly { a = 1; } ({ a }: a);
expected = true;
};
flake.tests."test function with no named arguments can take anything" = {
expr = takes { } (x: x);
expected = true;
};
flake.tests."test function called with non attrs" = {
expr = takes 22 ({ host }: [ host ]);
expected = false;
};
flake.tests."test function missing required attr" = {
expr = takes { } ({ host }: [ host ]);
expected = false;
};
flake.tests."test function satisfied required attr" = {
expr = takes {
host = 1;
} ({ host, ... }: [ host ]);
expected = true;
};
flake.tests."test function missing second required attr" = {
expr =
takes
{
host = 1;
}
(
{ host, user }:
[
host
user
]
);
expected = false;
};
flake.tests."test function optional second attr" = {
expr =
takes
{
host = 1;
foo = 9;
}
(
{
host,
user ? 0,
}:
[
host
user
]
);
expected = true;
};
in
{
inherit flake;
}

16
flake/den/flake.nix Normal file
View file

@ -0,0 +1,16 @@
{
outputs = _: {
flakeModule = ./nix/flakeModule.nix;
templates = {
default.path = ./templates/default;
default.description = "Minimal configuration";
examples.path = ./templates/examples;
examples.description = "API examples and CI";
bogus.path = ./templates/bogus;
bogus.description = "For bug reproduction";
};
packages = import ./nix/template-packages.nix;
namespace = import ./nix/namespace.nix;
lib = import ./nix/lib.nix;
};
}

View file

@ -0,0 +1,205 @@
{
inputs,
lib,
config,
...
}:
let
inherit (config) den;
hostsOption = lib.mkOption {
description = "den hosts definition";
default = { };
type = lib.types.attrsOf systemType;
};
systemType = lib.types.submodule (
{ name, ... }:
{
freeformType = lib.types.attrsOf (hostType name);
}
);
hostType =
system:
lib.types.submodule (
{ name, config, ... }:
{
freeformType = lib.types.attrsOf lib.types.anything;
options = {
name = strOpt "host configuration name" name;
hostName = strOpt "Network hostname" config.name;
system = strOpt "platform system" system;
class = strOpt "os-configuration nix class for host" (
if lib.hasSuffix "darwin" config.system then "darwin" else "nixos"
);
aspect = strOpt "main aspect name of <class>" config.name;
description = strOpt "host description" "${config.class}.${config.hostName}@${config.system}";
users = lib.mkOption {
description = "user accounts";
default = { };
type = lib.types.attrsOf userType;
};
instantiate = lib.mkOption {
description = ''
Function used to instantiate the OS configuration.
Depending on class, defaults to:
`darwin`: inputs.darwin.lib.darwinSystem
`nixos`: inputs.nixpkgs.lib.nixosSystem
`systemManager`: inputs.system-manager.lib.makeSystemConfig
Set explicitly if you need:
- a custom input name, eg, nixos-unstable.
- adding specialArgs when absolutely required.
'';
example = lib.literalExpression "inputs.nixpkgs.lib.nixosSystem";
type = lib.types.unspecified;
default =
{
nixos = inputs.nixpkgs.lib.nixosSystem;
darwin = inputs.darwin.lib.darwinSystem;
systemManager = inputs.system-manager.lib.makeSystemConfig;
}
.${config.class};
};
intoAttr = lib.mkOption {
description = ''
Flake attr where to add the named result of this configuration.
flake.<intoAttr>.<name>
Depending on class, defaults to:
`darwin`: darwinConfigurations
`nixos`: nixosConfigurations
`systemManager`: systemConfigs
'';
example = lib.literalExpression ''"nixosConfigurations"'';
type = lib.types.str;
default =
{
nixos = "nixosConfigurations";
darwin = "darwinConfigurations";
systemManager = "systemConfigs";
}
.${config.class};
};
mainModule = lib.mkOption {
internal = true;
visible = false;
readOnly = true;
type = lib.types.deferredModule;
default = mainModule config "OS" "host";
};
};
}
);
userType = lib.types.submodule (
{ name, config, ... }:
{
freeformType = lib.types.attrsOf lib.types.anything;
options = {
name = strOpt "user configuration name" name;
userName = strOpt "user account name" config.name;
class = strOpt "home management nix class" "homeManager";
aspect = strOpt "main aspect name" config.name;
};
}
);
strOpt =
description: default:
lib.mkOption {
type = lib.types.str;
inherit description default;
};
homesOption = lib.mkOption {
description = "den standalone home-manager configurations";
default = { };
type = lib.types.attrsOf homeSystemType;
};
homeSystemType = lib.types.submodule (
{ name, ... }:
{
freeformType = lib.types.attrsOf (homeType name);
}
);
homeType =
system:
lib.types.submodule (
{ name, config, ... }:
{
freeformType = lib.types.attrsOf lib.types.anything;
options = {
name = strOpt "home configuration name" name;
userName = strOpt "user account name" config.name;
system = strOpt "platform system" system;
class = strOpt "home management nix class" "homeManager";
aspect = strOpt "main aspect name" config.name;
description = strOpt "home description" "home.${config.userName}@${config.system}";
instantiate = lib.mkOption {
description = ''
Function used to instantiate the home configuration.
Depending on class, defaults to:
`homeManager`: inputs.home-manager.lib.homeManagerConfiguration
Set explicitly if you need:
- a custom input name, eg, home-manager-unstable.
- adding extraSpecialArgs when absolutely required.
'';
example = lib.literalExpression "inputs.home-manager.lib.homeManagerConfiguration";
type = lib.types.unspecified;
default =
{
homeManager = inputs.home-manager.lib.homeManagerConfiguration;
}
.${config.class};
};
intoAttr = lib.mkOption {
description = ''
Flake attr where to add the named result of this configuration.
flake.<intoAttr>.<name>
Depending on class, defaults to:
`homeManager`: homeConfigurations
'';
example = lib.literalExpression ''"homeConfigurations"'';
type = lib.types.str;
default =
{
homeManager = "homeConfigurations";
}
.${config.class};
};
mainModule = lib.mkOption {
internal = true;
visible = false;
readOnly = true;
type = lib.types.deferredModule;
default = mainModule config "HM" "home";
};
};
}
);
mainModule =
from: intent: name:
let
asp = den.aspects.${from.aspect};
ctx = {
${intent} = asp;
${name} = from;
};
mod = (asp ctx).resolve { inherit (from) class; };
in
mod;
in
{
inherit hostsOption homesOption;
}

View file

@ -0,0 +1,8 @@
# creates den.default aspect
{ lib, den, ... }:
{
config.den.default.__functor = den.lib.parametric.atLeast;
options.den.default = lib.mkOption {
type = den.lib.aspects.types.aspectSubmodule;
};
}

View file

@ -0,0 +1,34 @@
# create aspect dependencies from hosts/users
{
lib,
den,
...
}:
let
inherit (den.lib) parametric;
makeAspect = from: {
${from.aspect} = {
${from.class} = { };
includes = [ den.default ];
__functor = parametric.atLeast;
};
};
hosts = map builtins.attrValues (builtins.attrValues den.hosts);
homes = map builtins.attrValues (builtins.attrValues den.homes);
aspectClass = from: { inherit (from) aspect class; };
deps = lib.pipe hosts [
(lib.flatten)
(map (h: builtins.attrValues h.users))
(users: users ++ hosts ++ homes)
(lib.flatten)
(map aspectClass)
(lib.unique)
(map makeAspect)
];
in
{
den.aspects = lib.mkMerge deps;
}

View file

@ -0,0 +1,97 @@
{
den,
lib,
...
}:
let
inherit (den.lib)
owned
statics
parametric
;
inherit (den.lib.take) exactly;
dependencies = [
(exactly osDependencies)
(exactly hmUserDependencies)
(exactly hmStandaloneDependencies)
];
osDependencies =
{ OS, host }:
{
includes = [
(owned den.default)
(statics den.default)
(owned OS)
(statics OS)
{
includes =
let
users = builtins.attrValues host.users;
contrib = osUserDependencies { inherit OS host; };
in
map contrib users;
}
];
};
osUserDependencies =
{ OS, host }:
user:
let
USR = den.aspects.${user.aspect};
in
{
includes = [
(owned USR)
(statics USR)
(USR { inherit OS host user; })
];
};
# from OS home-managed integration.
hmUserDependencies =
{
OS-HM,
host,
user,
}:
let
inherit (OS-HM) OS HM;
in
{
includes = [
(owned den.default)
(statics den.default)
(owned HM)
(statics HM)
(owned OS)
(statics OS)
(parametric {
inherit
OS
HM
user
host
;
} OS)
];
};
hmStandaloneDependencies =
{ HM, home }:
{
includes = [
(owned den.default)
(statics den.default)
(owned HM)
(statics HM)
];
};
in
{
den.default.includes = dependencies;
}

View file

@ -0,0 +1,17 @@
{ lib, config, ... }:
{
options.den = lib.mkOption {
type = lib.types.submodule {
imports = [
(lib.mkAliasOptionModule [ "_" ] [ "provides" ])
];
options.provides = lib.mkOption {
default = { };
description = "Batteries Included - re-usable high-level aspects";
type = lib.types.submodule {
freeformType = lib.types.attrsOf config.den.lib.aspects.types.providerType;
};
};
};
};
}

View file

@ -0,0 +1,55 @@
{ lib, den, ... }:
let
description = ''
Defines a user at OS and Home levels.
Works in NixOS/Darwin and standalone Home-Manager
## Usage
# for NixOS/Darwin
den.aspects.my-user.includes = [ den._.define-user ]
# for standalone home-manager
den.aspects.my-home.includes = [ den._.define-user ]
or globally (automatically applied depending on context):
den.default.includes = [ den._.define-user ]
'';
homeDir =
host: user:
if lib.hasSuffix "darwin" host.system then "/Users/${user.userName}" else "/home/${user.userName}";
userContext =
{ host, user, ... }:
{
nixos.users.users.${user.userName}.isNormalUser = true;
darwin.users.users.${user.userName} = {
name = user.userName;
home = homeDir host user;
};
homeManager = {
home.username = user.userName;
home.homeDirectory = homeDir host user;
};
};
hmContext =
{ home, ... }:
userContext {
host.system = home.system;
user.userName = home.userName;
};
in
{
den.provides.define-user = {
inherit description;
includes = [
userContext
hmContext
];
__functor = den.lib.parametric.atLeast;
};
}

View file

@ -0,0 +1,76 @@
{
inputs,
lib,
den,
...
}:
let
description = ''
integrates home-manager into nixos/darwin OS classes.
usage:
for using home-manager in just a particular host:
den.aspects.my-laptop.includes = [ den._.home-manager ];
for enabling home-manager by default on all hosts:
den.default.includes = [ den._.home-manager ];
Does nothing for hosts that have no users with `homeManager` class.
Expects `inputs.home-manager` to exist. If `<host>.hm-module` exists
it is the home-manager.{nixos/darwin}Modules.home-manager.
For each user resolves den.aspects.''${user.aspect} and imports its homeManager class module.
'';
homeManager =
{ OS, host }:
{ class, aspect-chain }:
let
hmClass = "homeManager";
hmUsers = builtins.filter (u: u.class == hmClass) (lib.attrValues host.users);
hmUserModule =
user:
let
ctx = {
inherit aspect-chain;
class = hmClass;
};
HM = den.aspects.${user.aspect};
aspect = HM {
inherit host user;
OS-HM = { inherit OS HM; };
};
module = aspect.resolve ctx;
in
module;
users = map (user: {
name = user.userName;
value.imports = [ (hmUserModule user) ];
}) hmUsers;
hmModule = host.hm-module or inputs.home-manager."${class}Modules".home-manager;
aspect.${class} = {
imports = [ hmModule ];
home-manager.users = lib.listToAttrs users;
};
supportedOS = builtins.elem class [
"nixos"
"darwin"
];
enabled = supportedOS && builtins.length hmUsers > 0;
in
if enabled then aspect else { };
in
{
den.provides.home-manager = {
inherit description;
__functor = _: den.lib.take.exactly homeManager;
};
}

View file

@ -0,0 +1,77 @@
{
inputs,
den,
...
}:
{
den.provides.import-tree.description = ''
Recursively imports non-dendritic .nix files depending on their Nix configuration `class`.
This can be used to help migrating from huge existing setups.
```
# this is at <repo>/modules/non-dendritic.nix
den.aspects.my-laptop.includes = [
(den._.import-tree._.host ../non-dendritic)
]
```
With following structure, it will automatically load modules depending on their class.
```
<repo>/
modules/
non-dendritic.nix # configures this aspect
non-dendritic/ # name is just an example here
hosts/
my-laptop/
_nixos/ # a directory for `nixos` class
auto-generated-hardware.nix # any nixos module
_darwin/
foo.nix
_homeManager/
me.nix
```
## Requirements
- inputs.import-tree
## Usage
this aspect can be included explicitly on any aspect:
# example: will import ./disko/_nixos files automatically.
den.aspects.my-disko.includes = [ (den._.import-tree ./disko/) ];
or it can be default imported per host/user/home:
# load from ./hosts/<host>/_nixos
den.default.includes = [ (den._.import-tree._.host ./hosts) ];
# load from ./users/<user>/{_homeManager, _nixos}
den.default.includes = [ (den._.import-tree._.user ./users) ];
# load from ./homes/<home>/_homeManager
den.default.includes = [ (den._.import-tree._.home ./homes) ];
you are also free to create your own auto-imports layout following the implementation of these.
'';
den._.import-tree.__functor =
_: root:
# deadnix: skip
{ class, aspect-chain }:
let
path = "${toString root}/_${class}";
aspect.${class}.imports = [ (inputs.import-tree path) ];
in
if builtins.pathExists path then aspect else { };
den._.import-tree.provides = {
host = root: { host, ... }: den._.import-tree "${toString root}/${host.name}";
home = root: { home, ... }: den._.import-tree "${toString root}/${home.name}";
user = root: { user, ... }: den._.import-tree "${toString root}/${user.name}";
};
}

View file

@ -0,0 +1,37 @@
{ lib, ... }:
let
description = ''
Sets user as *primary*.
On NixOS adds wheel and networkmanager groups.
On Darwin sets user as system.primaryUser
On WSL sets wsl.defaultUser if host has an `wsl` attribute.
## Usage
den.aspects.my-user.includes = [ den._.primary-user ];
'';
userToHostContext =
{ user, host, ... }:
let
on-wsl.nixos.wsl.defaultUser = user.userName;
in
{
inherit description;
includes = lib.optionals (host ? wsl) [ on-wsl ];
darwin.system.primaryUser = user.userName;
nixos.users.users.${user.userName} = {
isNormalUser = true;
extraGroups = [
"wheel"
"networkmanager"
];
};
};
in
{
den.provides.primary-user = userToHostContext;
}

View file

@ -0,0 +1,22 @@
{ lib, ... }:
{
den.provides.unfree.description = ''
A class generic aspect that enables unfree packages by name.
Works for any class (nixos/darwin/homeManager,etc) on any host/user/home context.
## Usage
den.aspects.my-laptop.includes = [ (den._.unfree [ "code" ]) ];
It will dynamically provide a module for each class when accessed.
'';
den.provides.unfree.__functor =
_self: allowed-names:
# deadnix: allow
{ class, aspect-chain }:
{
${class}.nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) allowed-names;
};
}

View file

@ -0,0 +1,41 @@
{ den, ... }:
let
description = ''
Sets a user default shell, enables the shell at OS and Home level.
Usage:
den.aspects.vic.includes = [
# will always love red snappers.
(den._.user-shell "fish")
];
'';
userShell =
shell: user:
let
nixos =
{ pkgs, ... }:
{
programs.${shell}.enable = true;
users.users.${user.userName}.shell = pkgs.${shell};
};
darwin = nixos;
homeManager.programs.${shell}.enable = true;
in
{
inherit nixos darwin homeManager;
};
in
{
den.provides.user-shell = shell: {
inherit description;
__functor = den.lib.parametric.atLeast;
includes = [
({ user, ... }: userShell shell user)
({ home, ... }: userShell shell home)
];
};
}

View file

@ -0,0 +1,58 @@
{
config,
lib,
withSystem,
inputs,
...
}:
let
build =
builder: cfg:
let
items = map builtins.attrValues (builtins.attrValues cfg);
buildItem = item: {
inherit (item) name intoAttr;
value = builder item;
};
in
map buildItem (lib.flatten items);
osConfiguration =
host:
host.instantiate {
specialArgs = {
inherit inputs;
};
modules = [
host.mainModule
{ nixpkgs.hostPlatform = lib.mkDefault host.system; }
];
};
homeConfiguration =
home:
withSystem home.system (
{ pkgs, ... }:
home.instantiate {
inherit pkgs;
extraSpecialArgs = { inherit inputs; };
modules = [ home.mainModule ];
}
);
cfgs = (build osConfiguration config.den.hosts) ++ (build homeConfiguration config.den.homes);
outputs =
acc: item:
acc
// {
${item.intoAttr} = (acc.${item.intoAttr} or { }) // {
${item.name} = item.value;
};
};
in
{
flake = lib.foldl outputs { } cfgs;
}

14
flake/den/modules/lib.nix Normal file
View file

@ -0,0 +1,14 @@
{
lib,
inputs,
config,
...
}:
{
config.den.lib = inputs.den.lib { inherit inputs lib config; };
options.den.lib = lib.mkOption {
internal = true;
visible = false;
type = lib.types.attrsOf lib.types.raw;
};
}

View file

@ -0,0 +1,13 @@
{
inputs,
lib,
config,
...
}:
let
types = import ./_types.nix { inherit inputs lib config; };
in
{
options.den.hosts = types.hostsOption;
options.den.homes = types.homesOption;
}

View file

@ -0,0 +1,10 @@
{
inputs,
config,
lib,
...
}:
{
config._module.args.den = config.den;
imports = [ ((inputs.flake-aspects.lib lib).new-scope "den") ];
}

View file

@ -0,0 +1,55 @@
# __findFile implementation to resolve deep aspects.
# inspired by https://fzakaria.com/2025/08/10/angle-brackets-in-a-nix-flake-world
{
lib,
config,
...
}:
_nixPath: name:
let
findAspect =
path:
let
head = lib.head path;
tail = lib.tail path;
notFound = "Aspect not found: ${lib.concatStringsSep "." path}";
headIsDen = head == "den";
readFromDen = lib.getAttrFromPath ([ "den" ] ++ tail) config;
headIsAspect = builtins.hasAttr head config.den.aspects;
aspectsPath = [
"den"
"aspects"
] ++ path;
readFromAspects = lib.getAttrFromPath aspectsPath config;
headIsDenful = lib.hasAttrByPath [ "ful" head ] config.den;
denfulTail = if lib.head tail == "provides" then lib.tail tail else tail;
denfulPath = [
"den"
"ful"
head
] ++ denfulTail;
readFromDenful = lib.getAttrFromPath denfulPath config;
found =
if headIsDen then
readFromDen
else if headIsAspect then
readFromAspects
else if headIsDenful then
readFromDenful
else
throw notFound;
in
found;
in
lib.pipe name [
(lib.strings.replaceStrings [ "/" ] [ ".provides." ])
(lib.strings.splitString ".")
(findAspect)
]

View file

@ -0,0 +1,4 @@
{ inputs, ... }:
{
imports = [ (inputs.import-tree ../modules) ];
}

View file

@ -0,0 +1,24 @@
lib:
let
check =
params: func:
let
givenArgs = builtins.isAttrs params;
fargs = lib.functionArgs func;
provided = builtins.attrNames params;
args = lib.mapAttrsToList (name: optional: { inherit name optional; }) fargs;
required = map (x: x.name) (lib.filter (x: !x.optional) args);
intersection = lib.intersectLists required provided;
satisfied = givenArgs && lib.length required == lib.length intersection;
noExtras = lib.length required == lib.length provided;
exactly = satisfied && noExtras;
in
{
inherit satisfied exactly;
};
in
{
__functor = self: self.atLeast;
atLeast = params: func: (check params func).satisfied;
exactly = params: func: (check params func).exactly;
}

101
flake/den/nix/lib.nix Normal file
View file

@ -0,0 +1,101 @@
{
inputs,
lib,
config,
...
}:
let
# "Just Give 'Em One of These" - Moe Szyslak
# A __functor that applies context to parametric includes (functions)
funk =
apply: aspect:
aspect
// {
__functor = self: ctx: {
includes = builtins.filter (x: x != { }) (map (apply ctx) (builtins.filter isFn self.includes));
};
};
isFn = f: (builtins.isFunction f) || (f ? __functor);
canTake = import ./fn-can-take.nix lib;
# creates an aspect that inherits class from fromAspect.
owned =
aspect:
aspect
// {
includes = [ ];
__functor =
self:
# deadnix: skip
{ class, aspect-chain }:
self;
};
# only static includes from an aspect.
statics =
aspect:
aspect
// {
__functor =
self:
# deadnix: skip
{ class, aspect-chain }@ctx:
funk applyStatics self ctx;
};
applyStatics =
ctx: f:
if isStatic f then
f ctx
else if !isFn f then
f
else
{ };
isStatic = canTake {
class = "";
aspect-chain = [ ];
};
take.unused = _unused: used: used;
take.exactly = take canTake.exactly;
take.atLeast = take canTake.atLeast;
take.__functor =
_: takes: fn: ctx:
if takes ctx fn then fn ctx else { };
parametric.atLeast = funk (lib.flip take.atLeast);
parametric.exactly = funk (lib.flip take.exactly);
parametric.fixedTo = lib.flip parametric.atLeast;
parametric.expands = attrs: funk (ctx: (lib.flip take.atLeast) (ctx // attrs));
parametric.__functor =
self: ctx:
if ctx == true then
self.atLeast
else if ctx == false then
self.exactly
else if isFn ctx then
funk ctx
else
self.fixedTo ctx;
aspects = inputs.flake-aspects.lib lib;
__findFile = import ./den-brackets.nix { inherit lib config; };
den-lib = {
inherit
parametric
aspects
__findFile
statics
owned
isFn
canTake
take
;
};
in
den-lib

View file

@ -0,0 +1,29 @@
name: input:
{ config, lib, ... }:
let
isLocal = !builtins.isAttrs input;
isOutput = isLocal && input == true;
aliasModule = lib.mkAliasOptionModule [ name ] [ "den" "ful" name ];
type = lib.types.attrsOf config.den.lib.aspects.types.providerType;
source = if isLocal then { } else input.denful.${name};
output =
if isOutput then
{
config.flake.denful.${name} = config.den.ful.${name};
options.flake.denful.${name} = lib.mkOption { inherit type; };
}
else
{ };
in
{
imports = [
aliasModule
output
];
config._module.args.${name} = config.den.ful.${name};
config.den.ful.${name} = source;
options.den.ful.${name} = lib.mkOption { inherit type; };
}

View file

@ -0,0 +1,13 @@
let
rev = "9100a0f";
narHash = "sha256:09m84vsz1py50giyfpx0fpc7a4i0r1xsb54dh0dpdg308lp4p188";
compat = fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${rev}.tar.gz";
sha256 = narHash;
};
flake = import compat { src = ../templates/default; };
pkgs = flake.outputs.packages;
in
{
x86_64-linux.default = pkgs.x86_64-linux.vm;
}

View file

@ -0,0 +1,21 @@
on:
pull_request:
push:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
flake-check:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
name: Nix flake check
runs-on: ${{matrix.os}}
steps:
- uses: wimpysworld/nothing-but-nix@main
if: matrix.os == 'ubuntu-latest'
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: actions/checkout@v5
- run: nix flake metadata
- run: nix flake check

View file

@ -0,0 +1,17 @@
# Bug Reproduction den
Use this small template to reproduce bugs in den.
Create a **minimal** bug reproduction at [`modules/bug.nix`](modules/bug.nix)
Then run tests:
```console
nix flake check
```
Format code with:
```console
nix fmt
```

222
flake/den/templates/bogus/flake.lock generated Normal file
View file

@ -0,0 +1,222 @@
{
"nodes": {
"den": {
"locked": {
"lastModified": 1763285091,
"narHash": "sha256-F2OsALSm86dfNA2grd5MVRPpAWZf4ZuuCzpYG7H0P9g=",
"owner": "vic",
"repo": "den",
"rev": "227451c9a99fb084bc6b66878c7f6f3e75f625d2",
"type": "github"
},
"original": {
"owner": "vic",
"ref": "main",
"repo": "den",
"type": "github"
}
},
"flake-aspects": {
"locked": {
"lastModified": 1763284357,
"narHash": "sha256-mPMHkhpOIsj2lg+KIcapFd4uj2N/9mZZ6RZBo/p5O1c=",
"owner": "vic",
"repo": "flake-aspects",
"rev": "6a6d47f531ad57ac854cee689e84f2e28861ec49",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-aspects",
"type": "github"
}
},
"flake-file": {
"locked": {
"lastModified": 1762466468,
"narHash": "sha256-OTAbN2Vmdn6Km2AYN+G6QrCdQUtbqaJKAJATP0tGkB8=",
"owner": "vic",
"repo": "flake-file",
"rev": "6a7da81583a42ba339a06aed2d0f3ee4a164f265",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-file",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs-lib"
]
},
"locked": {
"lastModified": 1762440070,
"narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762463325,
"narHash": "sha256-33YUsWpPyeBZEWrKQ2a1gkRZ7i0XCC/2MYpU6BVeQSU=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "0562fef070a1027325dd4ea10813d64d2c967b39",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"import-tree": {
"locked": {
"lastModified": 1762327901,
"narHash": "sha256-AJ96FNj50DU0bTyIzAPkPOjCZTHqjURVjok8qoXvmqM=",
"owner": "vic",
"repo": "import-tree",
"rev": "90fa129798be99cde036b78658e89475710966a1",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"nix-auto-follow": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1754073254,
"narHash": "sha256-CQp/v2HQ7AtGJQqFGRZLHt4MZAK3NF94I6GDaRyhbsc=",
"owner": "fzakaria",
"repo": "nix-auto-follow",
"rev": "5baa00b79d4cc46523da0b8b3532c5163d151be4",
"type": "github"
},
"original": {
"owner": "fzakaria",
"repo": "nix-auto-follow",
"type": "github"
}
},
"nix-unit": {
"inputs": {
"flake-parts": [
"flake-parts"
],
"nixpkgs": [
"nixpkgs"
],
"treefmt-nix": [
"treefmt-nix"
]
},
"locked": {
"lastModified": 1762507096,
"narHash": "sha256-dE3CbZR8KRDdb3b4fhMnpvhEl6XB+UnrLezuFekQ2ME=",
"owner": "nix-community",
"repo": "nix-unit",
"rev": "0d7230bc54783b812a5817398885aec660a93051",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-unit",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1762361079,
"narHash": "sha256-lz718rr1BDpZBYk7+G8cE6wee3PiBUpn8aomG/vLLiY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ffcdcf99d65c61956d882df249a9be53e5902ea5",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"den": "den",
"flake-aspects": "flake-aspects",
"flake-file": "flake-file",
"flake-parts": "flake-parts",
"home-manager": "home-manager",
"import-tree": "import-tree",
"nix-auto-follow": "nix-auto-follow",
"nix-unit": "nix-unit",
"nixpkgs": "nixpkgs",
"nixpkgs-lib": [
"nixpkgs"
],
"systems": "systems",
"treefmt-nix": "treefmt-nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762410071,
"narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "97a30861b13c3731a84e09405414398fbf3e109f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -0,0 +1,77 @@
# DO-NOT-EDIT. This file was auto-generated using github:vic/flake-file.
# Use `nix run .#write-flake` to regenerate it.
{
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules);
inputs = {
den = {
url = "github:vic/den/main";
};
flake-aspects = {
url = "github:vic/flake-aspects";
};
flake-file = {
url = "github:vic/flake-file";
};
flake-parts = {
inputs = {
nixpkgs-lib = {
follows = "nixpkgs-lib";
};
};
url = "github:hercules-ci/flake-parts";
};
home-manager = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:nix-community/home-manager";
};
import-tree = {
url = "github:vic/import-tree";
};
nix-auto-follow = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:fzakaria/nix-auto-follow";
};
nix-unit = {
inputs = {
flake-parts = {
follows = "flake-parts";
};
nixpkgs = {
follows = "nixpkgs";
};
treefmt-nix = {
follows = "treefmt-nix";
};
};
url = "github:nix-community/nix-unit";
};
nixpkgs = {
url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
nixpkgs-lib = {
follows = "nixpkgs";
};
systems = {
url = "github:nix-systems/default";
};
treefmt-nix = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:numtide/treefmt-nix";
};
};
}

View file

@ -0,0 +1,27 @@
{ inputs, lib, ... }:
{
den.hosts.x86_64-linux.igloo.users.tux = { };
den.hosts.aarch64-darwin.apple.users.tim = { };
# Use aspects to create a **minimal** bug reproduction
den.aspects.igloo.nixos =
{ pkgs, ... }:
{
users.users.tux.packages = [ pkgs.hello ];
};
# rename "it works", evidently it has bugs
flake.tests."test it works" =
let
tux = inputs.self.nixosConfigurations.igloo.config.users.users.tux;
expr.len = lib.length tux.packages;
expr.names = map lib.getName tux.packages;
expected.len = 1;
expected.names = [ "hello" ];
in
{
inherit expr expected;
};
}

View file

@ -0,0 +1,7 @@
{ inputs, lib, ... }:
{
flake-file.inputs.flake-file.url = lib.mkDefault "github:vic/flake-file";
imports = [
inputs.flake-file.flakeModules.dendritic
];
}

View file

@ -0,0 +1,33 @@
# DO-NOT-CHANGE. Keep your reproduction minimalistic!
#
# try not adding new inputs
# but if you have no options (pun intended)
# here's the place.
#
# IF you make any change to this file, use:
# `nix run .#write-flake`
#
# We provide nix-unit and home-manager for common
# usage.
{ inputs, ... }:
{
# change "main" with a commit where bug is present
flake-file.inputs.den.url = "github:vic/den/main";
flake-file.inputs.nix-unit = {
url = "github:nix-community/nix-unit";
inputs.nixpkgs.follows = "nixpkgs";
inputs.flake-parts.follows = "flake-parts";
inputs.treefmt-nix.follows = "treefmt-nix";
};
flake-file.inputs.home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
imports = [
inputs.nix-unit.modules.flake.default
];
}

View file

@ -0,0 +1,12 @@
{
perSystem = {
treefmt = {
projectRootFile = "flake.nix";
programs.nixfmt.enable = true;
programs.nixfmt.excludes = [ ".direnv" ];
programs.deadnix.enable = true;
programs.mdformat.enable = true;
programs.yamlfmt.enable = true;
};
};
}

View file

@ -0,0 +1,8 @@
# DO-NOT-EDIT: nix-unit configuration.
{ lib, inputs, ... }:
{
perSystem.nix-unit = {
allowNetwork = lib.mkDefault true;
inputs = lib.mkDefault inputs;
};
}

View file

@ -0,0 +1,17 @@
{ den, ... }:
{
den.default.nixos.system.stateVersion = "25.11";
den.default.homeManager.home.stateVersion = "25.11";
den.default.darwin.system.stateVersion = 6;
den.default.includes = [
den._.home-manager
den._.define-user
den.aspects.no-boot
];
den.aspects.no-boot.nixos = {
boot.loader.grub.enable = false;
fileSystems."/".device = "/dev/fake";
};
}

View file

@ -0,0 +1,27 @@
on:
pull_request:
push:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
flake-check:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
name: Nix flake check ${{matrix.os}}
runs-on: ${{matrix.os}}
steps:
- uses: wimpysworld/nothing-but-nix@main
if: matrix.os == 'ubuntu-latest'
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- uses: actions/checkout@v5
- run: nix flake metadata
- run: |
cat <<-EOF > modules/ci-runtime.nix
{
_module.args.CI = true;
}
EOF
- run: nix flake check

View file

@ -0,0 +1,35 @@
# Getting Started Guide
Steps you can follow after cloning this template:
- Be sure to read the [den documentation](https://vic.github.io/den)
- Update den input.
```console
nix flake update den
```
- Run checks to test everything works.
```console
nix flake check
```
- Read [modules/den.nix](modules/den.nix) where hosts and homes definitions are for this example.
- Read [modules/namespace.nix](modules/namespace.nix) where a new `eg` (an example) aspects namespace is created.
- Read [modules/aspects/igloo.nix](modules/aspects/igloo.nix) where the `igloo` host is configured.
- Read [modules/aspects/alice.nix](modules/aspects/alice.nix) where the `alice` user is configured.
- Run the VM.
```console
nix run .#vm
```
- Edit and run VM loop.
Feel free to add more aspects, organize things to your liking.

173
flake/den/templates/default/flake.lock generated Normal file
View file

@ -0,0 +1,173 @@
{
"nodes": {
"darwin": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762627886,
"narHash": "sha256-/QLk1bzmbcqJt9sU43+y/3tHtXhAy0l8Ck0MoO2+evQ=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "5125a3cd414dc98bbe2c528227aa6b62ee61f733",
"type": "github"
},
"original": {
"owner": "nix-darwin",
"repo": "nix-darwin",
"type": "github"
}
},
"den": {
"locked": {
"lastModified": 1763285091,
"narHash": "sha256-F2OsALSm86dfNA2grd5MVRPpAWZf4ZuuCzpYG7H0P9g=",
"owner": "vic",
"repo": "den",
"rev": "227451c9a99fb084bc6b66878c7f6f3e75f625d2",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "den",
"type": "github"
}
},
"flake-aspects": {
"locked": {
"lastModified": 1763284357,
"narHash": "sha256-mPMHkhpOIsj2lg+KIcapFd4uj2N/9mZZ6RZBo/p5O1c=",
"owner": "vic",
"repo": "flake-aspects",
"rev": "6a6d47f531ad57ac854cee689e84f2e28861ec49",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-aspects",
"type": "github"
}
},
"flake-file": {
"locked": {
"lastModified": 1762732765,
"narHash": "sha256-sVxN4q4V8jhIAF44aPSMULIJMJ5eG5HtzcQgbr2UEBg=",
"owner": "vic",
"repo": "flake-file",
"rev": "7b037ae446102d3ccc5e23d3c52d5ada8e0fc4f3",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-file",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs-lib"
]
},
"locked": {
"lastModified": 1762810396,
"narHash": "sha256-dxFVgQPG+R72dkhXTtqUm7KpxElw3u6E+YlQ2WaDgt8=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "0bdadb1b265fb4143a75bd1ec7d8c915898a9923",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762787259,
"narHash": "sha256-t2U/GLLXHa2+kJkwnFNRVc2fEJ/lUfyZXBE5iKzJdcs=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "37a3d97f2873e0f68711117c34d04b7c7ead8f4e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"import-tree": {
"locked": {
"lastModified": 1762327901,
"narHash": "sha256-AJ96FNj50DU0bTyIzAPkPOjCZTHqjURVjok8qoXvmqM=",
"owner": "vic",
"repo": "import-tree",
"rev": "90fa129798be99cde036b78658e89475710966a1",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1762482733,
"narHash": "sha256-g/da4FzvckvbiZT075Sb1/YDNDr+tGQgh4N8i5ceYMg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e1ebeec86b771e9d387dd02d82ffdc77ac753abc",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"darwin": "darwin",
"den": "den",
"flake-aspects": "flake-aspects",
"flake-file": "flake-file",
"flake-parts": "flake-parts",
"home-manager": "home-manager",
"import-tree": "import-tree",
"nixpkgs": "nixpkgs",
"nixpkgs-lib": [
"nixpkgs"
],
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -0,0 +1,55 @@
# DO-NOT-EDIT. This file was auto-generated using github:vic/flake-file.
# Use `nix run .#write-flake` to regenerate it.
{
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules);
inputs = {
darwin = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:nix-darwin/nix-darwin";
};
den = {
url = "github:vic/den";
};
flake-aspects = {
url = "github:vic/flake-aspects";
};
flake-file = {
url = "github:vic/flake-file";
};
flake-parts = {
inputs = {
nixpkgs-lib = {
follows = "nixpkgs-lib";
};
};
url = "github:hercules-ci/flake-parts";
};
home-manager = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:nix-community/home-manager";
};
import-tree = {
url = "github:vic/import-tree";
};
nixpkgs = {
url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
nixpkgs-lib = {
follows = "nixpkgs";
};
systems = {
url = "github:nix-systems/default";
};
};
}

View file

@ -0,0 +1,70 @@
{ den, eg, ... }:
{
den.aspects.alice = {
# Alice can include other aspects.
# For small, private one-shot aspects, use let-bindings like here.
# for more complex or re-usable ones, define on their own modules,
# as part of any aspect-subtree.
includes =
let
# deadnix: skip # not required, showcasing angle-brackets syntax.
inherit (den.lib) __findFile;
customEmacs.homeManager =
{ pkgs, ... }:
{
programs.emacs.enable = true;
programs.emacs.package = pkgs.emacs30-nox;
};
in
[
# from local bindings.
customEmacs
# from the aspect tree, cooper example is defined bellow
den.aspects.cooper
den.aspects.setHost
# from the `eg` namespace.
eg.autologin
# den included batteries that provide common configs.
<den/primary-user> # alice is admin always.
(<den/user-shell> "fish") # default user shell
];
# Alice configures NixOS hosts it lives on.
nixos =
{ pkgs, ... }:
{
users.users.alice.packages = [ pkgs.vim ];
};
# Alice home-manager.
homeManager =
{ pkgs, ... }:
{
home.packages = [ pkgs.htop ];
};
# <user>.provides.<host>, via eg/routes.nix
provides.igloo =
{ host, ... }:
{
nixos.programs.nh.enable = host.name == "igloo";
};
};
# This is a context-aware aspect, that emits configurations
# **anytime** at least the `user` data is in context.
# read more at https://vic.github.io/den/context-aware.html
den.aspects.cooper =
{ user, ... }:
{
nixos.users.users.${user.userName}.description = "Alice Cooper";
};
den.aspects.setHost =
{ host, ... }:
{
networking.hostName = host.hostName;
};
}

View file

@ -0,0 +1,48 @@
{
config,
# deadnix: skip # enable <den/brackets> syntax for demo.
__findFile ? __findFile,
den,
...
}:
{
# Lets also configure some defaults using aspects.
# These are global static settings.
den.default = {
darwin.system.stateVersion = 6;
nixos.system.stateVersion = "25.05";
homeManager.home.stateVersion = "25.05";
};
# These are functions that produce configs
den.default.includes = [
# ${user}.provides.${host} and ${host}.provides.${user}
<eg/routes>
# Enable home-manager on all hosts.
<den/home-manager>
# Automatically create the user on host.
<den/define-user>
# Disable booting when running on CI on all NixOS hosts.
(if config ? _module.args.CI then <eg/ci-no-boot> else { })
# NOTE: be cautious when adding fully parametric functions to defaults.
# defaults are included on EVERY host/user/home, and IF you are not careful
# you could be duplicating config values. For example:
#
# # This will append 42 into foo option for the {host} and for EVERY {host,user}
# ({ host, ... }: { nixos.foo = [ 42 ]; }) # DO-NOT-DO-THIS.
#
# # Instead try to be explicit if a function is intended for ONLY { host }.
(den.lib.take.exactly (
# deadnix: skip
{ OS, host }:
{
nixos.networking.hostName = host.hostName;
}
))
];
}

View file

@ -0,0 +1,15 @@
{
# autologin is context-aware, parametric aspect.
# it applies only if the context has at least { user }
# meaning that has access to user data
eg.autologin =
{ user, ... }:
{
nixos =
{ config, lib, ... }:
lib.mkIf config.services.displayManager.enable {
services.displayManager.autoLogin.enable = true;
services.displayManager.autoLogin.user = user.userName;
};
};
}

View file

@ -0,0 +1,9 @@
{
eg.ci-no-boot = {
description = "Disables booting during CI";
nixos = {
boot.loader.grub.enable = false;
fileSystems."/".device = "/dev/null";
};
};
}

View file

@ -0,0 +1,37 @@
# This example implements an aspect "routing" pattern.
#
# Unlike `den.default` which is `parametric.atLeast`
# we use `parametric.fixedTo` here, which help us
# propagate an already computed context to all includes.
#
# This aspect, when installed in a `parametric.atLeast`
# will just forward the same context.
# The `mutual` helper returns an static configuration which
# is ignored by parametric aspects, thus allowing
# non-existing aspects to be just ignored.
#
# Be sure to read: https://vic.github.io/den/dependencies.html
# See usage at: defaults.nix, alice.nix, igloo.nix
#
{ den, eg, ... }:
{
# Usage: `den.default.includes [ eg.routes ]`
eg.routes =
let
inherit (den.lib) parametric;
# eg, `<user>._.<host>` and `<host>._.<user>`
mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { };
routes =
{ host, user, ... }@ctx:
{
__functor = parametric.fixedTo ctx;
includes = [
(mutual user host)
(mutual host user)
];
};
in
routes;
}

View file

@ -0,0 +1,16 @@
let
installer = variant: {
nixos =
{ modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/cd-dvd/installation-cd-${variant}.nix") ];
};
};
in
{
# make USB/VM installers.
eg.vm-bootable.provides = {
tui = installer "minimal";
gui = installer "graphical-base";
};
}

View file

@ -0,0 +1,15 @@
{ eg, ... }:
{
eg.vm.provides = {
gui.includes = [
eg.vm
eg.vm-bootable._.gui
eg.xfce-desktop
];
tui.includes = [
eg.vm
eg.vm-bootable._.tui
];
};
}

View file

@ -0,0 +1,19 @@
{
eg.xfce-desktop.nixos =
{ lib, ... }:
{
# https://gist.github.com/nat-418/1101881371c9a7b419ba5f944a7118b0
services.xserver = {
enable = true;
desktopManager = {
xterm.enable = false;
xfce.enable = true;
};
};
services.displayManager = {
defaultSession = lib.mkDefault "xfce";
enable = true;
};
};
}

View file

@ -0,0 +1,20 @@
{
den.aspects.igloo = {
# igloo host provides some home-manager defaults to its users.
homeManager.programs.direnv.enable = true;
# NixOS configuration for igloo.
nixos =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.hello ];
};
# <host>.provides.<user>, via eg/routes.nix
provides.alice =
{ user, ... }:
{
homeManager.programs.helix.enable = user.name == "alice";
};
};
}

View file

@ -0,0 +1,5 @@
{
den.hosts.x86_64-linux.igloo.users.alice = { };
den.hosts.aarch64-darwin.apple.users.alice = { };
den.homes.x86_64-linux.alice = { };
}

View file

@ -0,0 +1,6 @@
{ inputs, ... }:
{
imports = [
inputs.flake-file.flakeModules.dendritic
];
}

View file

@ -0,0 +1,37 @@
# This repo was generated with github:vic/flake-file#dendritic template.
# Run `nix run .#write-flake` after changing any input.
#
# Inputs can be placed in any module, the best practice is to have them
# as close as possible to their actual usage.
# See: https://vic.github.io/dendrix/Dendritic.html#minimal-and-focused-flakenix
#
# For our template, we enable home-manager and nix-darwin by default, but
# you are free to remove them if not being used by you.
{ inputs, ... }:
{
flake-file.inputs = {
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
darwin = {
url = "github:nix-darwin/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs";
};
## these stable inputs are for wsl
#nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
#home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
#home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
#nixos-wsl = {
# url = "github:nix-community/nixos-wsl";
# inputs.nixpkgs.follows = "nixpkgs-stable";
# inputs.flake-compat.follows = "";
#};
};
}

View file

@ -0,0 +1,14 @@
{ inputs, den, ... }:
{
# create an `eg` (example!) namespace.
imports = [ (inputs.den.namespace "eg" false) ];
# you can have more than one namespace, create yours.
# imports = [ (inputs.den.namespace "yours" true) ];
# you can also import namespaces from remote flakes.
# imports = [ (inputs.den.namespace "ours" inputs.theirs) ];
# this line enables den angle brackets syntax in modules.
_module.args.__findFile = den.lib.__findFile;
}

View file

@ -0,0 +1,34 @@
# Some CI checks to ensure this template always works.
# Feel free to adapt or remove when this repo is yours.
{ inputs, ... }:
{
perSystem =
{
pkgs,
self',
lib,
...
}:
let
checkCond = name: cond: pkgs.runCommandLocal name { } (if cond then "touch $out" else "");
apple = inputs.self.darwinConfigurations.apple.config;
igloo = inputs.self.nixosConfigurations.igloo.config;
alice-at-igloo = igloo.home-manager.users.alice;
vmBuilds = !pkgs.stdenvNoCC.isLinux || builtins.pathExists (self'.packages.vm + "/bin/vm");
iglooBuilds = !pkgs.stdenvNoCC.isLinux || builtins.pathExists (igloo.system.build.toplevel);
appleBuilds = !pkgs.stdenvNoCC.isDarwin || builtins.pathExists (apple.system.build.toplevel);
in
{
checks."igloo builds" = checkCond "igloo-builds" iglooBuilds;
checks."apple builds" = checkCond "apple-builds" appleBuilds;
checks."vm builds" = checkCond "vm-builds" vmBuilds;
checks."alice enabled igloo nh" = checkCond "alice.provides.igloo" igloo.programs.nh.enable;
checks."igloo enabled alice helix" =
checkCond "igloo.provides.alice" alice-at-igloo.programs.helix.enable;
checks."alice-custom-emacs" = checkCond "hm.programs.emacs.package" (
"emacs-nox" == lib.getName alice-at-igloo.programs.emacs.package
);
};
}

View file

@ -0,0 +1,22 @@
# enables `nix run .#vm`. it is very useful to have a VM
# you can edit your config an launch the VM to test stuff
# instead of having to reboot each time.
{ inputs, eg, ... }:
{
den.aspects.igloo.includes = [
eg.vm._.gui
# eg.vm._.tui
];
perSystem =
{ pkgs, ... }:
{
packages.vm = pkgs.writeShellApplication {
name = "vm";
text = ''
${inputs.self.nixosConfigurations.igloo.config.system.build.vm}/bin/run-igloo-vm "$@"
'';
};
};
}

276
flake/den/templates/examples/flake.lock generated Normal file
View file

@ -0,0 +1,276 @@
{
"nodes": {
"darwin": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762039661,
"narHash": "sha256-oM5BwAGE78IBLZn+AqxwH/saqwq3e926rNq5HmOulkc=",
"owner": "nix-darwin",
"repo": "nix-darwin",
"rev": "c3c8c9f2a5ed43175ac4dc030308756620e6e4e4",
"type": "github"
},
"original": {
"owner": "nix-darwin",
"repo": "nix-darwin",
"type": "github"
}
},
"den": {
"locked": {
"lastModified": 1763285091,
"narHash": "sha256-F2OsALSm86dfNA2grd5MVRPpAWZf4ZuuCzpYG7H0P9g=",
"owner": "vic",
"repo": "den",
"rev": "227451c9a99fb084bc6b66878c7f6f3e75f625d2",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "den",
"type": "github"
}
},
"flake-aspects": {
"locked": {
"lastModified": 1763284357,
"narHash": "sha256-mPMHkhpOIsj2lg+KIcapFd4uj2N/9mZZ6RZBo/p5O1c=",
"owner": "vic",
"repo": "flake-aspects",
"rev": "6a6d47f531ad57ac854cee689e84f2e28861ec49",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-aspects",
"type": "github"
}
},
"flake-file": {
"locked": {
"lastModified": 1761535278,
"narHash": "sha256-OPZ7XpG778i9CIJfchAwgrZGZ9z1dWJzfN18VFxCyS4=",
"owner": "vic",
"repo": "flake-file",
"rev": "57b2a65831ae49e4f9218dbba1c2dc9700f6cd68",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-file",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs-lib"
]
},
"locked": {
"lastModified": 1762040540,
"narHash": "sha256-z5PlZ47j50VNF3R+IMS9LmzI5fYRGY/Z5O5tol1c9I4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "0010412d62a25d959151790968765a70c436598b",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1762087455,
"narHash": "sha256-hpbPma1eUKwLAmiVRoMgIHbHiIKFkcACobJLbDt6ABw=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "43e205606aeb253bfcee15fd8a4a01d8ce8384ca",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"home-manager-stable": {
"inputs": {
"nixpkgs": [
"nixpkgs-stable"
]
},
"locked": {
"lastModified": 1758463745,
"narHash": "sha256-uhzsV0Q0I9j2y/rfweWeGif5AWe0MGrgZ/3TjpDYdGA=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "3b955f5f0a942f9f60cdc9cacb7844335d0f21c3",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.05",
"repo": "home-manager",
"type": "github"
}
},
"import-tree": {
"locked": {
"lastModified": 1761120675,
"narHash": "sha256-TEbh9zISiQcU82VwVoEbmXHnSGlUxTwvjJA9g9ErSDA=",
"owner": "vic",
"repo": "import-tree",
"rev": "a037ed2a58fc0ebed9e93b9ef79b0646e648f719",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"nix-auto-follow": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1754073254,
"narHash": "sha256-CQp/v2HQ7AtGJQqFGRZLHt4MZAK3NF94I6GDaRyhbsc=",
"owner": "fzakaria",
"repo": "nix-auto-follow",
"rev": "5baa00b79d4cc46523da0b8b3532c5163d151be4",
"type": "github"
},
"original": {
"owner": "fzakaria",
"repo": "nix-auto-follow",
"type": "github"
}
},
"nixos-wsl": {
"inputs": {
"flake-compat": [],
"nixpkgs": [
"nixpkgs-stable"
]
},
"locked": {
"lastModified": 1761969132,
"narHash": "sha256-0me4+e+1VxNuvySSw0voqMCWU/eUmTuth7f4+Q2jbUY=",
"owner": "nix-community",
"repo": "nixos-wsl",
"rev": "761582d6ab431549fe1396d2cd681e3fe9376020",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixos-wsl",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1761880412,
"narHash": "sha256-QoJjGd4NstnyOG4mm4KXF+weBzA2AH/7gn1Pmpfcb0A=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a7fc11be66bdfb5cdde611ee5ce381c183da8386",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1762081535,
"narHash": "sha256-+j+CUiaUoa87EhnSOqG5pwXdJWahP8vo6BE0ekssdzs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "2afc9d6e79b59ea9bcaf620d335623b0f7c2ce96",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"darwin": "darwin",
"den": "den",
"flake-aspects": "flake-aspects",
"flake-file": "flake-file",
"flake-parts": "flake-parts",
"home-manager": "home-manager",
"home-manager-stable": "home-manager-stable",
"import-tree": "import-tree",
"nix-auto-follow": "nix-auto-follow",
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs",
"nixpkgs-lib": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable",
"systems": "systems",
"treefmt-nix": "treefmt-nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1761311587,
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -0,0 +1,93 @@
# DO-NOT-EDIT. This file was auto-generated using github:vic/flake-file.
# Use `nix run .#write-flake` to regenerate it.
{
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules);
inputs = {
darwin = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:nix-darwin/nix-darwin";
};
den = {
url = "github:vic/den";
};
flake-aspects = {
url = "github:vic/flake-aspects";
};
flake-file = {
url = "github:vic/flake-file";
};
flake-parts = {
inputs = {
nixpkgs-lib = {
follows = "nixpkgs-lib";
};
};
url = "github:hercules-ci/flake-parts";
};
home-manager = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:nix-community/home-manager";
};
home-manager-stable = {
inputs = {
nixpkgs = {
follows = "nixpkgs-stable";
};
};
url = "github:nix-community/home-manager/release-25.05";
};
import-tree = {
url = "github:vic/import-tree";
};
nix-auto-follow = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:fzakaria/nix-auto-follow";
};
nixos-wsl = {
inputs = {
flake-compat = {
follows = "";
};
nixpkgs = {
follows = "nixpkgs-stable";
};
};
url = "github:nix-community/nixos-wsl";
};
nixpkgs = {
url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
nixpkgs-lib = {
follows = "nixpkgs";
};
nixpkgs-stable = {
url = "github:nixos/nixpkgs/release-25.05";
};
systems = {
url = "github:nix-systems/default";
};
treefmt-nix = {
inputs = {
nixpkgs = {
follows = "nixpkgs";
};
};
url = "github:numtide/treefmt-nix";
};
};
}

View file

@ -0,0 +1,5 @@
User TODO: REMOVE this directory (or disable its import from den.nix)
It is used to implement tests for all den features so we can validate at CI.
Use it as reference to see how den features are used, however, be aware that this might not be the best practices for file/aspect organization.

View file

@ -0,0 +1,6 @@
# this is a non-dendritic darwin class module file.
# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
{ ... }:
{
# see nix-darwin options.
}

View file

@ -0,0 +1,14 @@
# this is a non-dendritic nix class module file.
# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
#
# USER TODO: Remove this file.
# suppose this file was auto-generated by nixos-generate-config or some other hardware tooling.
{ lib, ... }:
{
# used in CI to test this file was actually imported.
options.auto-imported = lib.mkOption {
readOnly = true;
type = lib.types.bool;
default = true;
};
}

View file

@ -0,0 +1,27 @@
# Adds some checks for CI
{
perSystem =
{
pkgs,
checkFile,
rockhopper,
honeycrisp,
cam,
bob,
...
}:
let
checks.x86_64-linux = {
vm = checkFile "vm-builds" "${rockhopper.config.system.build.vm}/bin/run-rockhopper-vm";
hosts-rockhopper = checkFile "nixos-builds" rockhopper.config.system.build.toplevel;
homes-cam = checkFile "home-builds" cam.activation-script;
};
checks.aarch64-darwin = {
hosts-honeycrisp = checkFile "darwin-builds" honeycrisp.config.system.build.toplevel;
homes-bob = checkFile "darwin-home-builds" bob.activation-script;
};
in
{
checks = checks.${pkgs.system} or { };
};
}

View file

@ -0,0 +1,30 @@
{
# Including an static aspect should not cause duplicate definitions
den.aspects.alice.includes = [
{
homeManager =
{ pkgs, ... }:
{
programs.emacs.enable = true;
programs.emacs.package = pkgs.emacs30-nox;
};
}
];
perSystem =
{
checkCond,
alice-at-rockhopper,
lib,
...
}:
{
checks.alice-custom-emacs = checkCond "set uniquely via a static includes" (
let
expr = lib.getName alice-at-rockhopper.programs.emacs.package;
expected = "emacs-nox";
in
expr == expected
);
};
}

View file

@ -0,0 +1,35 @@
{ lib, ... }:
let
# A custom `nixos` class module that defines an option `names`.
# Used to test that we are not duplicating values from owned configs.
nixosNames = names: { options.${names} = lib.mkOption { type = lib.types.listOf lib.types.str; }; };
in
{
den.default.nixos.imports = [ (nixosNames "people") ];
den.default.includes = [
(
{ user, ... }:
{
nixos.people = [ user.name ];
}
)
];
den.aspects.rockhopper.includes = [
# Example: importing a third-party nixos module.
{ nixos.imports = [ (nixosNames "names") ]; }
];
den.aspects.rockhopper.nixos.names = [ "tux" ];
perSystem =
{ checkCond, rockhopper, ... }:
{
checks.rockhopper-default-people = checkCond "set from den.default for each user" (
rockhopper.config.people == [ "alice" ]
);
checks.rockhopper-names-single-entry = checkCond "custom nixos array option set once" (
rockhopper.config.names == [ "tux" ]
);
};
}

View file

@ -0,0 +1,27 @@
{ den, ... }:
{
den.default.includes = [
# Example: parametric over many contexts: { home }, { host, user }, { fromUser, toHost }
den.provides.define-user
];
perSystem =
{
checkCond,
rockhopper,
adelie,
...
}:
{
checks.alice-exists-on-rockhopper = checkCond "den.default.user.includes defines user on host" (
rockhopper.config.users.users.alice.isNormalUser
);
checks.alice-not-exists-on-adelie = checkCond "den.default.user.includes defines user on host" (
!adelie.config.users.users ? alice
);
checks.will-exists-on-adelie = checkCond "den.default.user.includes defines user on host" (
adelie.config.users.users.will.isNormalUser
);
};
}

View file

@ -0,0 +1,8 @@
{ inputs, ... }:
{
# Example: adelie host using github:nix-community/NixOS-WSL
den.aspects.adelie.nixos = {
imports = [ inputs.nixos-wsl.nixosModules.default ];
wsl.enable = true;
};
}

View file

@ -0,0 +1,37 @@
{ self, ... }:
{
perSystem =
{ pkgs, ... }:
let
checkFile =
name: file:
pkgs.runCommandLocal name { } ''
ls -la ${file} | tee $out
'';
checkCond =
name: cond:
let
code = if cond then "touch $out" else ''echo "Cond-Failed: ${name}"'';
in
pkgs.runCommandLocal name { } code;
rockhopper = self.nixosConfigurations.rockhopper;
honeycrisp = self.darwinConfigurations.honeycrisp;
adelie = self.wslConfigurations.adelie;
cam = self.homeConfigurations.cam;
bob = self.homeConfigurations.bob;
luke = self.homeConfigurations.luke;
alice-at-rockhopper = rockhopper.config.home-manager.users.alice;
alice-at-honeycrisp = honeycrisp.config.home-manager.users.alice;
in
{
_module.args = {
inherit checkCond checkFile;
inherit rockhopper honeycrisp adelie;
inherit cam bob luke;
inherit alice-at-rockhopper alice-at-honeycrisp;
};
};
}

View file

@ -0,0 +1,13 @@
{
# Example: host provides static config to all its users hm.
den.aspects.rockhopper.homeManager.programs.direnv.enable = true;
perSystem =
{ checkCond, alice-at-rockhopper, ... }:
{
checks.host-contributes-to-user = checkCond "rockhopper contributes to all its users" (
alice-at-rockhopper.programs.direnv.enable
);
};
}

View file

@ -0,0 +1,42 @@
{ lib, ... }:
let
# Example: configuration that depends on both host and user. provides only to HM.
host-to-user-conditional =
{
user,
host,
...
}:
if user.userName == "alice" && !lib.hasSuffix "darwin" host.system then
{
homeManager.programs.git.enable = true;
}
else
{ };
in
{
den.aspects.rockhopper.includes = [
# Example: host provides parametric user configuration.
host-to-user-conditional
];
perSystem =
{
checkCond,
alice-at-rockhopper,
alice-at-honeycrisp,
...
}:
{
checks.alice-hm-git-enabled-on = checkCond "home-managed git for alice at rockhopper" (
alice-at-rockhopper.programs.git.enable
);
checks.alice-hm-git-enabled-off = checkCond "home-managed git for alice at honeycrisp" (
!alice-at-honeycrisp.programs.git.enable
);
};
}

View file

@ -0,0 +1,28 @@
# configures class-automatic module auto imports for hosts/users/homes.
# See documentation at modules/aspects/provides/import-tree.nix
{
# deadnix: skip
__findFile ? __findFile,
...
}:
{
# alice imports non-dendritic <class> modules from _non_dendritic/alice/_<class>/*.nix
den.aspects.alice.includes = [ (<den/import-tree> ./_non_dendritic/alice) ];
# See the documentation at batteries/import-tree.nix
den.default.includes = [
(<den/import-tree/host> ./_non_dendritic/hosts)
(<den/import-tree/user> ./_non_dendritic/users)
(<den/import-tree/home> ./_non_dendritic/homes)
];
# tests
perSystem =
{ checkCond, rockhopper, ... }:
{
checks.import-tree = checkCond "auto-imported from rockhopper/_nixos" (
rockhopper.config.auto-imported
);
};
}

View file

@ -0,0 +1,5 @@
{ inputs, den, ... }:
{
imports = [ (inputs.den.namespace "eg" false) ];
_module.args.__findFile = den.lib.__findFile;
}

View file

@ -0,0 +1,41 @@
let
# Example: adds hello into each user. provides only to OS.
hello-package-for-user =
{
user,
host,
...
}:
{
${host.class} =
{ pkgs, ... }:
{
users.users.${user.userName}.packages = [ pkgs.hello ];
};
};
in
{
den.default.includes = [ hello-package-for-user ];
perSystem =
{
checkCond,
rockhopper,
lib,
...
}:
{
checks.alice-hello-enabled-by-default = checkCond "added hello at user packages" (
let
progs = rockhopper.config.users.users.alice.packages;
expr = map lib.getName progs;
expected = [ "hello" ];
in
expr == expected
);
};
}

View file

@ -0,0 +1,29 @@
{ den, ... }:
{
den.aspects.alice.includes = [
# alice is always admin in all its hosts
den._.primary-user
];
den.aspects.will.includes = [
# will is primary user in WSL NixOS.
den._.primary-user
];
perSystem =
{
checkCond,
honeycrisp,
adelie,
...
}:
{
checks.alice-primary-on-macos = checkCond "den._.primary-user sets macos primary" (
honeycrisp.config.system.primaryUser == "alice"
);
checks.will-is-wsl-default = checkCond "wsl.defaultUser defined" (
adelie.config.wsl.defaultUser == "will"
);
};
}

View file

@ -0,0 +1,29 @@
{
den.default.includes =
let
# Example: parametric host aspect to automatically set hostName on any host.
set-host-name =
{ host, ... }:
{
${host.class}.networking.hostName = host.name;
};
in
[ set-host-name ];
perSystem =
{
checkCond,
rockhopper,
honeycrisp,
...
}:
{
checks.rockhopper-hostname = checkCond "den.default.host.includes sets hostName" (
rockhopper.config.networking.hostName == "rockhopper"
);
checks.honeycrisp-hostname = checkCond "den.default.host.includes sets hostName" (
honeycrisp.config.networking.hostName == "honeycrisp"
);
};
}

View file

@ -0,0 +1,31 @@
let
# Example: luke standalone home-manager has access to rockhopper osConfig specialArg.
os-conditional-hm =
{ home, ... }:
{
# access osConfig, wired via extraSpecialArgs in homes.nix.
homeManager =
{ osConfig, ... }:
{
programs.bat.enable = osConfig.programs.${home.programToDependOn}.enable;
};
};
in
{
# Example: standalone-hm config depends on osConfig (non-recursive)
# NOTE: this will only work for standalone hm, and not for hosted hm
# since a hosted hm configuration cannot depend on the os configuration.
den.aspects.luke.includes = [
os-conditional-hm
];
perSystem =
{ checkCond, luke, ... }:
{
checks.luke-hm-depends-on-osConfig = checkCond "standalone hm can depend on osConfig" (
luke.config.programs.bat.enable
);
};
}

View file

@ -0,0 +1,47 @@
# it is possible for top-level aspects directly under
# den.aspects to take a context argument.
{ den, lib, ... }:
let
# A module to test that toplevel had context.
topLevel = name: {
config.tops = name;
options.tops = lib.mkOption { type = lib.types.str; };
};
in
{
den.aspects.toplevel-user =
{ user, ... }:
{
nixos.imports = [ (topLevel user.name) ];
};
den.aspects.toplevel-host =
{ host, ... }:
{
homeManager.imports = [ (topLevel host.name) ];
};
den.aspects.alice.includes = [
den.aspects.toplevel-host
den.aspects.toplevel-user
];
perSystem =
{
checkCond,
alice-at-rockhopper,
rockhopper,
...
}:
{
checks.alice-toplevel-user = checkCond "alice toplevel param aspect" (
rockhopper.config.tops == "alice"
);
checks.alice-toplevel-host = checkCond "alice toplevel param aspect" (
alice-at-rockhopper.tops == "rockhopper"
);
};
}

View file

@ -0,0 +1,6 @@
{ den, ... }:
{
# cam uses unfree vscode.
den.aspects.cam.homeManager.programs.vscode.enable = true;
den.aspects.cam.includes = [ (den._.unfree [ "vscode" ]) ];
}

View file

@ -0,0 +1,12 @@
{
# Example: user provides static config to all its nixos hosts.
den.aspects.alice.nixos.users.users.alice.description = "Alice Q. User";
perSystem =
{ checkCond, rockhopper, ... }:
{
checks.user-contributes-to-host = checkCond "alice.nixos sets on rockhopper host" (
rockhopper.config.users.users.alice.description == "Alice Q. User"
);
};
}

View file

@ -0,0 +1,12 @@
{
# globally enable fish on all homes ever.
den.default.homeManager.programs.fish.enable = true;
perSystem =
{ checkCond, alice-at-rockhopper, ... }:
{
checks.alice-hm-fish-enabled-by-default = checkCond "home-managed fish for alice" (
alice-at-rockhopper.programs.fish.enable
);
};
}

View file

@ -0,0 +1,38 @@
{ lib, ... }:
let
# Example: configuration that depends on both host and user. provides anytime { user, host } is in context.
user-to-host-conditional =
{ user, host, ... }:
if user.userName == "alice" && !lib.hasSuffix "darwin" host.system then
{
nixos.programs.tmux.enable = true;
}
else
{ };
in
{
# Example: user provides parametric host configuration.
den.aspects.alice.includes = [
user-to-host-conditional
];
perSystem =
{
checkCond,
rockhopper,
honeycrisp,
...
}:
{
checks.alice-os-tmux-enabled-on = checkCond "os tmux for hosts having alice" (
rockhopper.config.programs.tmux.enable
);
checks.alice-os-tmux-enabled-off = checkCond "os tmux for hosts having alice" (
!honeycrisp.config.programs.tmux.enable
);
};
}

View file

@ -0,0 +1,17 @@
{ den, lib, ... }:
{
den.aspects.will.includes = [
# will has always loved red snappers
(den._.user-shell "fish")
];
perSystem =
{ checkCond, adelie, ... }:
{
checks.will-always-love-you = checkCond "red-snapper fish is default shell" (
"fish" == lib.getName adelie.config.users.users.will.shell
);
};
}

View file

@ -0,0 +1,13 @@
{
# Example: enable helix for alice on all its home-managed hosts.
den.aspects.alice.homeManager.programs.helix.enable = true;
perSystem =
{ checkCond, alice-at-rockhopper, ... }:
{
checks.alice-hm-helix-enabled-by-user = checkCond "home-managed helix for alice" (
alice-at-rockhopper.programs.helix.enable
);
};
}

View file

@ -0,0 +1,16 @@
let
# Example: A static aspect for vm installers.
vm-bootable = {
nixos =
{ modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix") ];
};
};
in
{
den.default.includes = [
# Example: static aspect
vm-bootable
];
}

View file

@ -0,0 +1,14 @@
# User TODO: Remove this file.
{
# default aspect can be used for global static settings.
den.default = {
# static values.
darwin.system.stateVersion = 6;
nixos.system.stateVersion = "25.05";
homeManager.home.stateVersion = "25.05";
# these defaults are set for checking with CI.
nixos.programs.vim.enable = true;
darwin.programs.zsh.enable = true;
};
}

View file

@ -0,0 +1,12 @@
{ den, ... }:
{
# see batteries/home-manager.nix
den.default.includes = [ den._.home-manager ];
# enable home-manager dependency.
flake-file.inputs.home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
}

View file

@ -0,0 +1,31 @@
# Example standalone home-manager configurations.
# These are independent of any host configuration.
# See documentation at <den>/nix/types.nix
{ inputs, ... }:
{
den.homes.x86_64-linux.cam = { };
den.homes.aarch64-darwin.bob = {
userName = "robert";
aspect = "developer";
};
# Example: custom home-manager instantiate for passing extraSpecialArgs.
den.homes.x86_64-linux.luke =
let
osConfig = inputs.self.nixosConfigurations.rockhopper.config;
in
{
# Example: luke standalone-homemanager needs access to rockhopper osConfig.
instantiate =
{ pkgs, modules }:
inputs.home-manager.lib.homeManagerConfiguration {
inherit pkgs modules;
extraSpecialArgs.osConfig = osConfig;
};
# Example: custom attribute instead of specialArgs
programToDependOn = "vim";
};
}

View file

@ -0,0 +1,50 @@
# This is a fully working example configuration.
# Feel free to remove it, adapt or split into several modules.
# See documentation at <den>/nix/types.nix
{ inputs, ... }:
{
den.hosts.aarch64-darwin.honeycrisp.users.alice = { };
den.hosts.aarch64-linux.emperor.users.alice = { };
den.hosts.x86_64-linux = {
rockhopper = {
description = "rockhopper is a kind of penguin";
users.alice = { };
};
adelie = {
description = "wsl on windows";
users.will = { };
intoAttr = "wslConfigurations";
# custom nixpkgs channel.
instantiate = inputs.nixpkgs-stable.lib.nixosSystem;
# custom attribute (see home-manager.nix)
hm-module = inputs.home-manager-stable.nixosModules.home-manager;
# custom attribute (see primary-user.nix)
wsl = { };
};
};
# move these inputs to any module you want.
# they are here for all our examples to work on CI.
flake-file.inputs = {
# these stable inputs are for wsl
nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
nixos-wsl = {
url = "github:nix-community/nixos-wsl";
inputs.nixpkgs.follows = "nixpkgs-stable";
inputs.flake-compat.follows = "";
};
darwin = {
url = "github:nix-darwin/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}

View file

@ -0,0 +1,10 @@
# USER TODO: remove this file.
# copy any desired module to your ./modules and let it be auto-imported.
{ inputs, ... }:
{
imports = [
# The _example directory contains CI tests for all den features.
# use it as reference of usage, but not of best practices.
(inputs.import-tree ./_example)
];
}

View file

@ -0,0 +1,7 @@
{ inputs, lib, ... }:
{
flake-file.inputs.flake-file.url = lib.mkDefault "github:vic/flake-file";
imports = [
inputs.flake-file.flakeModules.dendritic
];
}