diff --git a/README.md b/README.md
index 0545f94..e601b9f 100644
--- a/README.md
+++ b/README.md
@@ -18,15 +18,17 @@
-- Dendritic: same concern, different classes and context-aware.
+- Dendritic: each module configures **same** concern over **different** Nix classes.
-- Small, [DRY](modules/aspects/provides/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) modules.
+- Create [DRY](modules/aspects/provides/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) modules.
- [Parametric](modules/aspects/provides/define-user.nix) over `host`/`home`/`user`.
-- [Share](templates/examples/modules/_profile/namespace.nix) aspects across systems & repos.
+- [Share](templates/default/modules/namespace.nix) aspects across systems & repos.
-- Bidirectional [dependencies](modules/aspects/dependencies.nix): user/host contributions.
+- Context-aware [dependencies](modules/aspects/dependencies.nix): user/host contributions.
+
+- [Routable](templates/default/modules/aspects/eg/routes.nix) configurations.
- Custom factories for any Nix `class`.
@@ -38,7 +40,11 @@
- [Batteries](modules/aspects/provides/): Opt-in, replaceable aspects.
-- [Well-tested](templates/examples/modules/_example/ci) with [examples](templates/examples).
+- Opt-in [``](https://vic.github.io/den/angle-brackets.html) aspect resolution.
+
+- Templates [tested](templates/default/modules/tests.nix) along [examples](templates/examples/modules/_example/ci).
+
+- Concepts [documented](https://vic.github.io/den).
Need more batteries? See [vic/denful](https://github.com/vic/denful).
@@ -52,15 +58,21 @@ See schema in [`_types.nix`](modules/_types.nix).
```nix
# modules/hosts.nix
-# OS & standalone homes share 'vic' aspect.
-# $ nixos-rebuild switch --flake .#my-laptop
-# $ home-manager switch --flake .#vic
{
- den.hosts.x86-64-linux.laptop.users.vic = {};
+ # same home-manager vic configuration
+ # over laptop, macbook and standalone-hm
+ den.hosts.x86_64-linux.lap.users.vic = {};
+ den.hosts.aarch64-darwin.mac.users.vic = {};
den.homes.aarch64-darwin.vic = {};
}
```
+```console
+$ nixos-rebuild switch --flake .#lap
+$ darwin-rebuild switch --flake .#mac
+$ home-manager switch --flake .#vic
+```
+
🧩 [Aspect-oriented](https://github.com/vic/flake-aspects) incremental features. ([example](templates/default/modules/den.nix))
Any module can contribute configurations to aspects.
@@ -87,7 +99,7 @@ Any module can contribute configurations to aspects.
# User contribs to host
nixos.users.users = {
vic.description = "oeiuwq";
- }
+ };
includes = [
den.aspects.tiling-wm
den._.primary-user
diff --git a/nix/den-brackets.nix b/nix/den-brackets.nix
index 2fd6b21..1ff95e7 100644
--- a/nix/den-brackets.nix
+++ b/nix/den-brackets.nix
@@ -1,9 +1,5 @@
# __findFile implementation to resolve deep aspects.
# inspired by https://fzakaria.com/2025/08/10/angle-brackets-in-a-nix-flake-world
-#
-# For user facing documentation, see:
-# See templates/default/_profile/den-brackets.nix
-# See templates/default/_profile/namespace.nix
{
lib,
config,
@@ -21,14 +17,23 @@ let
notFound = "Aspect not found: ${lib.concatStringsSep "." path}";
headIsDen = head == "den";
- readFromDen = lib.getAttrFromPath tail config.den;
+ readFromDen = lib.getAttrFromPath ([ "den" ] ++ tail) config;
headIsAspect = builtins.hasAttr head config.den.aspects;
- readFromAspects = lib.getAttrFromPath path 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;
- readFromDenful = lib.getAttrFromPath ([ head ] ++ denfulTail) config.den.ful;
+ denfulPath = [
+ "den"
+ "ful"
+ head
+ ] ++ denfulTail;
+ readFromDenful = lib.getAttrFromPath denfulPath config;
found =
if headIsDen then
diff --git a/templates/default/modules/aspects/alice.nix b/templates/default/modules/aspects/alice.nix
index 289baed..9274926 100644
--- a/templates/default/modules/aspects/alice.nix
+++ b/templates/default/modules/aspects/alice.nix
@@ -30,5 +30,12 @@
{
home.packages = [ pkgs.htop ];
};
+
+ # .provides., via eg/routes.nix
+ provides.igloo =
+ { host, ... }:
+ {
+ nixos.programs.nh.enable = host.name == "igloo";
+ };
};
}
diff --git a/templates/default/modules/aspects/defaults.nix b/templates/default/modules/aspects/defaults.nix
index 845f906..00df1c5 100644
--- a/templates/default/modules/aspects/defaults.nix
+++ b/templates/default/modules/aspects/defaults.nix
@@ -16,6 +16,9 @@
# These are functions that produce configs
den.default.includes = [
+ # ${user}.provides.${host} and ${host}.provides.${user}
+
+
# Enable home-manager on all hosts.
diff --git a/templates/default/modules/aspects/eg/routes.nix b/templates/default/modules/aspects/eg/routes.nix
new file mode 100644
index 0000000..ec6f4df
--- /dev/null
+++ b/templates/default/modules/aspects/eg/routes.nix
@@ -0,0 +1,53 @@
+# This example implements an aspect "routing" pattern.
+#
+# Unlike `den.default` which is `parametric.atLeast` we use `parametric.exactly` here
+# to be more strict and prevent multiple values inclusion.
+#
+# 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;
+
+ os-from-user =
+ {
+ user,
+ host,
+ # deadnix: skip
+ OS,
+ # deadnix: skip
+ fromUser,
+ }:
+ parametric { inherit user host; } (mutual user host);
+
+ hm-from-host =
+ {
+ user,
+ host,
+ # deadnix: skip
+ HM,
+ # deadnix: skip
+ fromHost,
+ }:
+ parametric { inherit user host; } (mutual host user);
+
+ mutual = from: to: {
+ includes = [
+ # eg, `._.` and `._.`
+ (den.aspects.${from.aspect}._.${to.aspect} or { })
+ ];
+ };
+
+ in
+ {
+ __functor = parametric.exactly;
+ includes = [
+ os-from-user
+ hm-from-host
+ ];
+ };
+}
diff --git a/templates/default/modules/aspects/igloo.nix b/templates/default/modules/aspects/igloo.nix
index b5c1e31..eab55eb 100644
--- a/templates/default/modules/aspects/igloo.nix
+++ b/templates/default/modules/aspects/igloo.nix
@@ -16,5 +16,12 @@
eg.vm-bootable
eg.xfce-desktop
];
+
+ # .provides., via eg/routes.nix
+ provides.alice =
+ { user, ... }:
+ {
+ homeManager.programs.helix.enable = user.name == "alice";
+ };
};
}
diff --git a/templates/default/modules/tests.nix b/templates/default/modules/tests.nix
new file mode 100644
index 0000000..3a841b2
--- /dev/null
+++ b/templates/default/modules/tests.nix
@@ -0,0 +1,25 @@
+# Some CI checks to ensure this template always works.
+# Feel free to adapt or remove when this repo is yours.
+{ inputs, ... }:
+{
+ perSystem =
+ { pkgs, self', ... }:
+ 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;
+ };
+}
diff --git a/templates/examples/modules/_example/README.md b/templates/examples/modules/_example/README.md
index 39d3caf..687bbc5 100644
--- a/templates/examples/modules/_example/README.md
+++ b/templates/examples/modules/_example/README.md
@@ -1,15 +1,5 @@
User TODO: REMOVE this directory (or disable its import from den.nix)
-It is used to implement tests for all den feature so we can validate at CI.
+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.
-
-For a more "real-world" layout, see the `_profile` directory
-which is somewhat inspired on the
-dendritic implementation at [vic/vix](https://github.com/vic/vix/tree/8c8c7b8).
-
-However, feel free to also not use any predefined layout, explore by yourself
-and find out how things work for you. Be sure to share your insights with
-the [community](https://github.com/vic/den/discussions)
+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.
diff --git a/templates/examples/modules/_example/ci/import-tree.nix b/templates/examples/modules/_example/ci/import-tree.nix
index 8a7b02b..a9a8da2 100644
--- a/templates/examples/modules/_example/ci/import-tree.nix
+++ b/templates/examples/modules/_example/ci/import-tree.nix
@@ -1,7 +1,7 @@
# configures class-automatic module auto imports for hosts/users/homes.
# See documentation at modules/aspects/provides/import-tree.nix
{
- # deadnix: skip # see _profile/den-brackets.nix
+ # deadnix: skip
__findFile ? __findFile,
...
}:
diff --git a/templates/examples/modules/_example/ci/namespace.nix b/templates/examples/modules/_example/ci/namespace.nix
new file mode 100644
index 0000000..766a95f
--- /dev/null
+++ b/templates/examples/modules/_example/ci/namespace.nix
@@ -0,0 +1,5 @@
+{ inputs, den, ... }:
+{
+ imports = [ (inputs.den.namespace "eg" false) ];
+ _module.args.__findFile = den.lib.__findFile;
+}
diff --git a/templates/examples/modules/_profile/README.md b/templates/examples/modules/_profile/README.md
deleted file mode 100644
index 3bddbd8..0000000
--- a/templates/examples/modules/_profile/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-User TODO: REMOVE this directory (or disable its import from den.nix).
-Move any module you find useful from here into your /modules directory.
-
-This directory contains a bare-bones layout for using den.
-It is inspired on the patterns shown in the
-dendritic implementation at [vic/vix](https://github.com/vic/vix/tree/den).
-
-Use it as reference of how to organize things.
-
-Feel free to adapt the layout, explore by yourself
-and find out how things work for you. Be sure to share your insights with
-the [community](https://github.com/vic/den/discussions)
diff --git a/templates/examples/modules/_profile/den-brackets.nix b/templates/examples/modules/_profile/den-brackets.nix
deleted file mode 100644
index 3af0726..0000000
--- a/templates/examples/modules/_profile/den-brackets.nix
+++ /dev/null
@@ -1,36 +0,0 @@
-# This enables den's angle brackets opt-in feature.
-# Remove this file to opt-out.
-#
-# When den.lib.__findFile is in scope, you can do:
-#
-# and it will resolve to:
-# den.aspects.pro.provides.foo.provides.bar
-#
-# resolves to:
-# den.aspects.pro.provides.foo.includes
-#
-# resolves to:
-# den.provides.import-tree.provides.home
-#
-# resolves to den.default
-#
-# When the vix remote namespace is enabled
-# resolves to: den.ful.vix.provides.foo
-#
-# Usage:
-#
-# Bring `__findFile` into scope from module args:
-#
-# { __findFile, ... }:
-# den.default.includes = [ ];
-# }
-#
-# IF you are using nixf-diagnose, it will complain
-# about __findFile not being used, trick it with:
-#
-# { __findFile ? __findFile, ... }
-#
-{ den, ... }:
-{
- _module.args.__findFile = den.lib.__findFile;
-}
diff --git a/templates/examples/modules/_profile/hosts.nix b/templates/examples/modules/_profile/hosts.nix
deleted file mode 100644
index 2dc1f21..0000000
--- a/templates/examples/modules/_profile/hosts.nix
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- den.hosts.x86_64-linux.bones.users.fido = { };
-}
diff --git a/templates/examples/modules/_profile/hosts/bones/common-user-env.nix b/templates/examples/modules/_profile/hosts/bones/common-user-env.nix
deleted file mode 100644
index 07b5c8a..0000000
--- a/templates/examples/modules/_profile/hosts/bones/common-user-env.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-# An aspect that contributes to any user home on the bones host.
-{ ... }:
-let
- # private aspects can be let-bindings
- # more re-usable ones are better defined inside the `pro` namespace.
- host-contrib-to-user =
- { hostToUser, ... }:
- if hostToUser.host.name == "bones" || hostToUser.user.name == "fido" then
- {
- homeManager.programs.vim.enable = true;
- }
- else
- { };
-in
-{
- den.default.includes = [
- host-contrib-to-user
- ];
-}
diff --git a/templates/examples/modules/_profile/namespace.nix b/templates/examples/modules/_profile/namespace.nix
deleted file mode 100644
index 9d81577..0000000
--- a/templates/examples/modules/_profile/namespace.nix
+++ /dev/null
@@ -1,67 +0,0 @@
-# This module creates an aspect namespace.
-#
-# Just add the following import:
-#
-# # define local namespace. enable flake output.
-# imports = [ (inputs.den.namespace "vix" true) ];
-#
-# # you can use remote namespaces and they will merge
-# imports = [ (inputs.den.namespace "vix" inputs.dendrix) ];
-#
-# Internally, a namespace is just a `provides` branch:
-#
-# # den.ful is the social-convention for namespaces.
-# den.ful.
-#
-# Having an aspect namespace is not required but helps a lot
-# with organization and conventient access to your aspects.
-#
-# The following examples use the `vix` namespace,
-# inspired by github:vic/vix own namespace pattern.
-#
-# By using an aspect namespace you can:
-#
-# - Directly write to aspects in your namespace.
-#
-# {
-# vix.gaming.nixos = ...;
-#
-# # instead of:
-# # den.ful.vix.gaming.nixos = ...;
-# }
-#
-# - Directly read aspects from your namespace.
-#
-# # Access the namespace from module args
-# { vix, ... }:
-# {
-# den.default.includes = [ vix.security ];
-#
-# # instead of:
-# # den.default.includes = [ den.ful.vix.security ];
-# }
-#
-# - Share and re-use aspects between Dendritic flakes
-#
-# # Aspects opt-in exposed as flake.denful.
-# { imports = [( inputs.den.namespace "vix" true)] }
-#
-# # Many flakes can expose to the same namespace and we
-# # can merge them, accessing aspects in a uniform way.
-# { imports = [( inputs.den.namespace "vix" inputs.dendrix )] }
-#
-# - Use angle-brackets to access deeply nested trees
-#
-# # Be sure to read _profile/den-brackets.nix
-# { __findFile, ... }:
-# den.aspects.my-laptop.includes = [ ];
-# }
-#
-#
-# You can of course choose to not have any of the above.
-# USER TODO: Remove this file for not using a namespace.
-# USER TODO: Replace `pro` and update other files using it.
-{ inputs, ... }:
-{
- imports = [ (inputs.den.namespace "pro" true) ];
-}
diff --git a/templates/examples/modules/_profile/profiles.nix b/templates/examples/modules/_profile/profiles.nix
deleted file mode 100644
index dfbf837..0000000
--- a/templates/examples/modules/_profile/profiles.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-# Profiles are just aspects whose only job is to include other aspects
-# based on the properties (context) of the host/user they are included in.
-{ pro, den, ... }:
-{
-
- # install profiles as parametric aspects on all hosts/users
- den.default.includes = [
- pro.profiles
- ];
-
- pro.profiles = {
- __functor = den.lib.parametric true;
- includes = [
- ({ host, ... }: pro.${host.system} or { })
- # add other routes according to context.
- ];
- };
-
-}
diff --git a/templates/examples/modules/_profile/profiles/linux-utils-for-macos.nix b/templates/examples/modules/_profile/profiles/linux-utils-for-macos.nix
deleted file mode 100644
index b8a8a90..0000000
--- a/templates/examples/modules/_profile/profiles/linux-utils-for-macos.nix
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-
- # example custom profile per platform system, see profiles.nix
- pro.aarch64-darwin.darwin =
- { pkgs, ... }:
- {
- # provide a consistent environment with linux.
- environment.systemPackages = [
- pkgs.coreutils
- pkgs.util-linux
- ];
- };
-
-}
diff --git a/templates/examples/modules/_profile/profiles/single-user-is-admin.nix b/templates/examples/modules/_profile/profiles/single-user-is-admin.nix
deleted file mode 100644
index 8ee4c84..0000000
--- a/templates/examples/modules/_profile/profiles/single-user-is-admin.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-{ den, lib, ... }:
-{
-
- # When a host includes *ONLY* one user, make that user the admin.
- pro.single-user-is-admin =
- { userToHost, ... }@context:
- let
- inherit (userToHost) user host;
- single = 1 == builtins.length (builtins.attrValues host.users);
- exists = single && builtins.hasAttr user.name host.users;
- admin = lib.optionals exists [ den._.primary-user ];
- in
- {
- __functor = den.lib.parametric context;
- includes = [ den._.define-user ] ++ admin;
- };
-}
diff --git a/templates/examples/modules/_profile/users/fido/common-host-env.nix b/templates/examples/modules/_profile/users/fido/common-host-env.nix
deleted file mode 100644
index 0246263..0000000
--- a/templates/examples/modules/_profile/users/fido/common-host-env.nix
+++ /dev/null
@@ -1,16 +0,0 @@
-# An aspect that contributes to any operating system where fido is a user.
-# hooks itself into any host.
-{ pro, ... }:
-let
- fido-at-host =
- { userToHost, ... }:
- if userToHost.user.name != "fido" then { } else pro.fido._.${userToHost.host.name};
-in
-{
- den.default.includes = [
- fido-at-host
- ];
-
- # fido on bones host.
- pro.fido._.bones.nixos = { };
-}
diff --git a/templates/examples/modules/den.nix b/templates/examples/modules/den.nix
index 48918c0..7f908dc 100644
--- a/templates/examples/modules/den.nix
+++ b/templates/examples/modules/den.nix
@@ -6,8 +6,5 @@
# 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)
-
- # The _profile directory contains a minimal profile-based layout.
- (inputs.import-tree ./_profile)
];
}