den

repository·main·Indexed 19 days ago

https://github.com/denful/den

An aspect-oriented, context-driven Nix configuration framework. Den allows developers to define features as composable functions called aspects that apply configuration across multiple Nix classes (such as NixOS, Darwin, or Home Manager) based on provided context. It is built on four primary conceptual pillars: Entities (what exists), Aspects (what it does), Policies (how entities relate), and Quirks (shared structured data).

Tokens
72.9K
Snippets
249
Records
345
Agent score
66%

What's inside den

  1. Core capabilities of Den

    main

    Den provides a structured way to manage complex Nix infrastructure by addressing several key configuration challenges:

    • Entity Declaration: Define hosts and users using a structured path: den.hosts.<arch>.<hostName>.users.<userName>.
    • Schema Definition: Define common options/schemas for entities: den.schema.host.options.vpn-group = lib.mkOption.
    • Global Features: Include features that affect all entities via den.default.includes.
    • Cross-Entity Configuration: Allow a Host to affect its Users configuration and vice-versa.
    • Aspect Mixins: Mixin aspects from remote sources and enhance local ones using den.namespace.
    • Community Namespacing: Provide features to the community using a common namespace (e.g., { omfnix, ... }: { omfnix.niri.nixos = ...; }).
  2. Visualize aspect resolution graphs with den-diagram

    main

    The diag library allows you to visualize how Den resolves aspects, including aspect inclusions, policy fan-out, and class contributions. It works by transforming structured trace data from the effects pipeline into a format-agnostic Graph Intermediate Representation (Graph IR), which can then be filtered and rendered into various formats like Mermaid, GraphViz DOT, PlantUML, and C4.

    The Pipeline

    1. Trace capture: Collects structuredTrace entries from aspect resolution.
    2. Graph construction: Builds the format-agnostic IR (nodes, edges, stages, etc.).
    3. Filtering: Prunes, folds, or reshapes the IR (e.g., using class slices or user-declared-only filters).
    4. Rendering: Emits the diagram string in the target format.
  3. What is an Aspect and how does it work?

    main

    An Aspect is an attribute set that contains configuration modules for different Nix class types (such as nixos, homeManager, darwin, or hjem).

    Instead of configuring a host by pushing modules downward, Den uses an aspect-oriented model where features (aspects) are the primary organizational unit. An aspect consolidates all class-specific configuration for a single cross-cutting concern (e.g., gaming or bluetooth).

    Key distinction: Den Context vs NixOS Module Args Den context (e.g., { host }, { host, user }) is a set of parameters passed to an aspect function. This is not the same as NixOS module arguments ({ config, pkgs, lib, ... }). Den context is evaluated before module evaluation, which prevents infinite recursion loops common in traditional Nix configurations.

    # An aspect configuring a single concern across different Nix classes
    gaming = { host, user }: {
      nixos = { ... };
      homeManager = { ... };
      hjem = { ... };
      darwin = { ... };
    }
  4. What is an aspect in Den?

    main

    In Den, an aspect is a function of context that returns configuration for multiple Nix classes simultaneously. Unlike a standard NixOS module which typically returns a single configuration set, a Den aspect can return configurations for nixos, darwin, or any other Nix configuration class.

    This allows for parametric, cross-class Nix configurations that can be shared and reused. Because aspects are functions, they can be parameterized with custom arguments (using the Nix __functor pattern) and can be nested, avoiding the flat-structure limitations of standard flake.modules.

    # A standard NixOS module (returns one config):
    { pkgs, ... }: { <nixos-settings> }
    
    # A Den aspect (returns multiple configs):
    { host, user }: {
      nixos = { pkgs, ... }: { <nixos-settings> };
      darwin = { pkgs, ... }: { <darwin-settings> };
    }
  5. What is a fleet and how does the scope tree work?

    main

    In Den, a fleet is the set of hosts resolved together in a single pipeline run. All hosts declared in a Den flake automatically form a fleet by becoming sibling scopes in a shared scope tree.

    Scopes form a tree rooted at the flake. Built-in policies drive the hierarchy:

    • flake-to-systems: Creates flake-system scopes per system.
    • system-to-os-outputs: Creates host scopes per host.
    • system-to-hm-outputs: Creates home scopes per standalone home.
    • host-to-users: Creates user scopes per user on each host.

    Because hosts of the same system share the same flake-system parent, they are siblings, which allows them to share data via pipe.collect.

  6. What is a Den aspect?

    main

    In Den, an aspect is a function that takes a context (such as { host, user }) and returns configuration for multiple Nix classes simultaneously. Instead of scattering a single feature across separate nixos, darwin, or homeManager files, an aspect encapsulates the entire feature in one place.

    Key characteristics of aspects:

    • Composable: Aspects can include other aspects using the includes key.
    • Nesting: Aspects can provide capabilities that other aspects can consume using the provides key.
    • Conditional by Design: Because an aspect is a function of context, it only runs where the required context (like a user) exists, eliminating the need for explicit mkIf or enable flags.
    # An aspect is a function of context that returns
    # configuration for many Nix classes at once.
    den.aspects.gaming = { host, user }: {
      nixos       = { pkgs, ... }: { programs.steam.enable = true; };
      darwin      = { pkgs, ... }: { /* ... */ };
      homeManager = { pkgs, ... }: { /* ... */ };
    
      includes = [ den.aspects.performance ];   # aspects compose
      provides.emulation = { nixos = { /* ... */ }; };  # and nest
    };
  7. Configure Base Schemas with den.schema

    main

    The den.schema.{host,user,home} modules allow you to define meta-configuration that applies to all entities of that type. This is used to specify features for all hosts, add schema options with defaults, or apply global configuration.

    • Global Features: Use den.schema.host.<feature>.enable = true to enable a feature across all hosts.
    • Custom Options: Define new options with defaults using den.schema.<type>.
    • Global Attributes: Use den.schema.conf to apply attributes to every host, user, and home.
    {
      # Enable a feature for all hosts
      den.schema.host.home-manager.enable = true;
    
      # Add a new schema option with a default for all users
      den.schema.user = { user, lib, ... }: {
        options.groupName = lib.mkOption { default = user.userName; };
      };
    
      # Apply a configuration attribute to every host, user, and home
      den.schema.conf = {
        options.copyright = lib.mkOption { default = "Copy-Left"; };
      };
    }
  8. How parametric dispatch works in Den

    main

    Den uses parametric functions to handle context-aware configuration. A function's argument shape determines when it is executed during the resolution pipeline:

    • { host }: ... runs only in host contexts.
    • { host, user }: ... runs only in user contexts.

    Den automatically introspects these function arguments to perform dispatch. While an explicit den.lib.parametric wrapper exists, it is deprecated; you should simply define your function with the desired argument shape, and the pipeline will handle the dispatch automatically.

  9. Two patterns for building MicroVMs with Den

    main

    Den supports two primary patterns for integrating MicroVM.nix:

    1. Runnable MicroVM as Package: A standalone NixOS configuration that runs as a MicroVM directly as an application (a flake package).

      • Use nix run .#<package-name> to execute.
    2. Declarative Guest VMs on Host: Guest MicroVMs are declared on a host and managed together as part of the host's configuration.

      • Use nixos-rebuild build --flake .#<host-name> to build the host and its guests.
  10. What are Parametric Aspects and how to use them

    main

    A parametric aspect is an aspect defined as a function where the arguments are pipeline context values (e.g., host, user, home, or custom entity kinds). Den uses the function's argument pattern to determine when to activate the aspect.

    Key behaviors:

    • Automatic Activation: The aspect only runs in contexts where all required arguments are available. No mkIf or enable flags are required; the argument shape is the condition.
    • Static Attrs: If an aspect is a static attribute set (not a function), it is always included regardless of context.

    Examples

    Single argument activation:

    # This aspect only activates in {host} contexts
    den.aspects.networking = { host, ... }: {
      nixos.networking.hostName = host.name;
    };

    Multi-argument activation:

    # This only activates when both {host, user} are present
    den.aspects.user-groups = { host, user, ... }: {
      nixos.users.users.${user.userName}.extraGroups = [ "wheel" ];
    };

    Standalone context activation:

    # This only activates for standalone {home} contexts
    den.aspects.shell-config = { home, ... }: {
      homeManager.programs.zsh.enable = true;
    };
    den.aspects.networking = { host, ... }: {
      nixos.networking.hostName = host.name;
    };
  11. Understand the NVF Standalone project structure

    main

    A project initialized with the nvf-standalone template typically follows this structure:

    • flake.nix: Defines dependencies, including NVF.
    • modules/den.nix: Exposes the standalone Neovim package and Den aspects.
    • modules/nvf-integration.nix: Contains the den.lib.nvf helper, which handles class forwarding and resolution.
    • modules/header.txt: A dashboard header used by the mine variant.

    Key Integration Concepts

    • Runnable App: The flake exposes specific applications, such as .#my-neovim or .#your-neovim.
    • Custom Forward Class: Uses a class (e.g., vim) that forwards configuration into nvf.vim.
    • Aspect-to-Module Resolution: The function den.lib.aspects.resolve "nvf" aspect is used to transform an aspect tree into a standard NixOS-style module compatible with NVF's neovimConfiguration.
  12. Provide configuration across entities using `provides`

    main

    To establish a relationship between a User and a Host, use the provides namespace within an aspect definition. The target of the provides key determines where the configuration is delivered:

    • provides.<name>: Delivers configuration to the specific host or user named <name>.
    • provides.to-hosts: Delivers configuration to every host where the aspect's owner lives.
    • provides.to-users: Delivers configuration to every user residing on the host where the aspect's owner lives.

    Note that provides keys registered on a user aspect are subtree-scoped: they reach that user and its hosts, but they cannot reach sibling users on the same host. To configure multiple users or perform per-user selection, you must register the provides key on the host aspect.

    # user aspect provides to a specific host or to all hosts where it lives
    den.aspects.tux = {
      provides.igloo.nixos.programs.emacs.enable = true;
      provides.to-hosts = { host, ... }: {
        nixos.programs.nh.enable = host.name == "igloo";
      };
    };
    
    # host aspect provides to a specific user or to all its users
    den.aspects.igloo = {
      provides.alice.homeManager.programs.vim.enable = true;
      provides.to-users = { user, ... }: {
        homeManager.programs.helix.enable = user.name == "alice";
      };
    };