Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented
Nix 100.0%
114 4 8

Clone this repository

https://tangled.org/oeiuwq.com/den https://tangled.org/did:plc:hwcqoy35x55nzde2sm6dbvq7/den
git@tangled.org:oeiuwq.com/den git@tangled.org:did:plc:hwcqoy35x55nzde2sm6dbvq7/den

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

Sponsor Vic Dendritic Nix License CI Status

den - an aspect-oriented approach to Dendritic Nix configurations.#

den and vic's dendritic libs made for you with Love++ and AI--. If you like my work, consider sponsoring

den
  • Dendritic: same concern, different classes and context-aware.

  • Small, DRY & class-generic modules.

  • Parametric over host/home/user.

  • Share aspects across systems & repos.

  • Bidirectional dependencies: user/host contributions.

  • Custom factories for any Nix class.

  • Use stable/unstable channels per config.

  • Freeform host/user/home schemas (no specialArgs).

  • Multi-platform, multi-tenant hosts.

  • Batteries: Opt-in, replaceable aspects.

  • Well-tested with an example suite.

Need more batteries? See vic/denful.

Join our community discussion.

🏠 Define Hosts, Users & Homes concisely.

See schema in _types.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 = {};
  den.homes.aarch64-darwin.vic = {};
}

🧩 Aspect-oriented incremental features. (example)

Any module can contribute configurations to aspects.

# modules/my-laptop.nix
{ den, ... }: {
  den.aspects.my-laptop = {
    includes = [
      den.aspects.workplace-vpn
      den.provides.home-manager
    ];
    nixos  = { /* NixOS options */ };
    darwin = { /* nix-darwin options */ };
    # For all users of my-laptop
    homeManager.programs.vim.enable = true;
  };
}

# modules/vic.nix
{ den, ... }: {
  den.aspects.vic = {
    homeManager = { /* ... */ };
    # User contribs to host
    nixos.users.users = {
      vic.description = "oeiuwq";
    }
    includes = [ 
      den.aspects.tiling-wm 
      den._.primary-user 
    ];
  };
}

For real-world examples, see vic/vix or this GH search.

❄️ Try it now!

Launch our template VM:

nix run github:vic/den

Or, initialize a project:

nix flake init -t github:vic/den
nix flake update den
nix run .#vm

Our default template provides a profile-based layout for a quick start.

You are done! You know everything to start creating configurations with den.

Feel free to to explore the codebase, particularly our included batteries and tests.

Learn More#

If you want to learn how den works, the following sections detail its concepts and patterns.

Basic Concepts#

Learn about static vs. parametric aspects, the default aspect, and dependencies.

den has two fundamental types of aspects: static and parametric.

Static aspects are attribute sets.#

An aspect is a set of modules for different classes, configuring a single concern across them.

den.aspects.my-laptop = {
  nixos  = { /* ... */ };
  darwin = { /* ... */ };

  # Nested aspects via `_`
  # (alias for `provides`)
  _.gaming = {
    nixos = { /* ... */ };
    # Dependency graph via `includes`
    includes = [ den.aspects.nvidia-gpu ];
  };
};

Parametric aspects are functions.#

They take a context and return a configuration.

# A `{ host, gaming }` contextual aspect.
hostFunction = { host, gaming }: {
  nixos.networking.hostName = host.hostName;
};

# A parametric aspect can request context-aware
# configurations from other aspects.
hostParametric = { host }: {
  __functor = den.lib.parametric {
    inherit host;
    gaming.emulators = [ "nes" ];
  };
  includes = [ hostFunction den.default ];
}

den uses several default contexts to manage dependencies:

  • { host }, { user, host }, { home }
  • { fromUser, toHost }, { fromHost, toUser }

The Default Aspect and Dependencies#

den features a special aspect, den.default, which applies global configurations. It is automatically included in all hosts, users, and homes, receiving the appropriate context (e.g., den.default { inherit home; }).

You can register static values or context-aware parametric aspects within den.default. This allows you to define defaults that apply conditionally to all hosts or users.

A key feature of den is its management of aspect dependencies. When an aspect is resolved, flake-aspects includes its class-specific module along with those of its transitive includes. den extends this by registering special dependencies to link hosts and users. For instance, a host's configuration includes contributions from each of its users' aspects, and a user's home configuration includes contributions from its host's aspect, creating a bidirectional flow of settings. This is achieved without recursion by using distinct contexts for each direction.

Organization Patterns#

Learn patterns for organizing and reusing aspects.

While den is unopinionated about organization, these patterns can help structure your aspects for clarity and reuse.

Aspect Namespaces#

Group related aspects under a single top-level aspect and alias it as a module argument for easier access. The den.namespace function streamlines this.

Using local and remote namespaces#

# modules/namespace.nix
{ inputs, ... }:
{
  imports = [ 
    # create local `pro` namespace
    (inputs.den.namespace "pro" true) 
    # mixin remote `pro` from input
    (inputs.den.namespace "pro" inputs.foo)
  ];
}

Directly read and write to namespace#

# modules/my-laptop.nix
{ pro, ... }:
{
  pro.gaming.includes = [ pro.gpu ];
}

Angle-Bracket Syntax#

For deeply nested aspects, den offers an experimental feature to shorten access paths. By bringing den.lib.__findFile into scope, you can use angle brackets to reference aspects more concisely.

  • <my-laptop> resolves to den.aspects.my-laptop
  • <my-laptop/gaming> resolves to den.aspects.my-laptop.provides.gaming
  • <den/import-tree/host> resolves to den.provides.import-tree.provides.home
  • <vix/foo/bar> namespace aware: den.ful.vix.foo.provides.bar

This feature is powered by a custom __findFile implementation. See the profile example to learn how to enable it.

Parametric Routing#

You can use parametric aspects to create routing logic that dynamically includes other aspects based on context. This pattern allows you to build a flexible and declarative dependency tree.

# modules/routes.nix
{ den, pro, ... }:
let
  # Route to a platform-specific profile
  by-platform = { host }: pro.${host.system} or { };
in 
{
  # Apply routes globally
  den.default.includes = [ by-platform ];
}

You made it to the end! Thanks for reading. I hope you enjoy using den. It is feature-complete and unlikely to change.

Contributing#

Contributions are welcome! Feel free to fix typos, improve documentation, or share ideas in our discussions.

All PRs are checked against the CI. New features should include a test in _example/ci.nix.

To run tests locally:

nix flake check ./checkmate --override-input target .
nix flake check ./templates/examples --override-input den .

Ensure code is formatted:

nix run ./checkmate#fmt --override-input target .

If you have found a bug, please open a discussion (issues are for agreed, actionable items).

We provide a bogus template you can use to create a minimal bug reproduction repository. Please share your repo.

mkdir bogus && cd bogus
nix flake init -t github:vic/den#bogus
nix flake update den
nix flake check