Skip to content

Configure Aspects

Den automatically creates an aspect for each of your host/user/home defintions. For example, with the following definitions:

den.hosts.x86_64-linux.igloo.users = {
tux = {
classes = [ "homeManager" "hjem" ];
};
pingu = {
classes = [ "maid" ];
};
};

Den will create the following parametric aspects for you:

den.aspects.igloo = parametric {
nixos = {}; # `host.class` was derived from the host platform
};
den.aspects.tux = parametric {
homeManager = {};
hjem = {};
};
den.aspects.pingu = parametric {
maid = {};
};

In addition to per-class modules, an aspect has an includes list referencing other aspects, and provides for sub-aspects in same category:

{ den, ... }: {
den.aspects.laptop = {
# Owned configs per class
nixos = { pkgs, ... }: {
environment.systemPackages = [ pkgs.git ];
};
darwin.nix-homebrew.enable = true;
homeManager.programs.starship.enable = true;
# Dependencies
includes = [
den.provides.primary-user
den.aspects.gaming.provides.emulation
];
};
den.aspects.gaming = {
darwin = ...;
nixos = ...;
provides.emulation = {
darwin = ...;
nixos = ...;
};
}
}

Attributes named after a Nix class (nixos, darwin, homeManager, or any custom class) are referred by this documentation as owned configs.

They are just a regular Nix module:

  • Attrset Module: plain and simple configuration.
  • Function Module: taking module arguments ({ config, pkgs, lib, ... }: { }).
# Attrset form
den.aspects.laptop.nixos.networking.hostName = "laptop";
# Function form
den.aspects.laptop.nixos = { pkgs, ... }: {
environment.systemPackages = [ pkgs.git ];
};

includes is a list of aspects used to declare dependencies between aspects.

Unlike other Nix libraries that use stringly-typed “tags” to define requirements, Den uses real aspect references, these can be type-checked by the Nix module system.

den.aspects.workstation.includes = [
# Reference another aspect (its full DAG is included)
den.aspects.dev-tools
];

It is important to note the three kinds of values that Den distinguishes as part of an includes list:

  1. Static (plain attribute set): { nixos.foo = ...; }
  2. Static (aspects leaf): {class, aspect-chain}: { ${class}.foo = ...; }
  3. Parametric (any other function): { host, user }: { ${host.class}.foo = ...; }

(1) and (2) are termed static aspects and are the terminal leafs of aspects DAG. (1) provides configuration unconditionally, and (2) gets access to the class that is being resolved and an aspect-chain that lead to the current aspect (most recent last).

(3) is a more interesting kind of aspect that is used by Den to pass host/user defitions into these functions, so they can inspect the host features and provide configuration accordingly.

provides creates named sub-aspects accessible via den.aspects.<name>._.<sub> or den.aspects.<name>.provides.<sub>:

den.aspects.tools.provides.editors = {
homeManager.programs.helix.enable = true;
homeManager.programs.vim.enable = true;
};
# Used elsewhere:
den.aspects.alice.includes = [ den.aspects.tools._.editors ];

provides can also be parametric functions:

den.aspects.alice.provides.work-vpn = { host, user, ... }:
lib.optionalAttrs host.hasVpn {
nixos.services.openvpn.servers.work.config = "...";
};

den.default is a special aspect applied to every host, user, and home:

{
den.default = {
nixos.system.stateVersion = "25.11";
homeManager.home.stateVersion = "25.11";
includes = [
den.provides.define-user
den.provides.inputs'
];
};
}

Aspects have a meta submodule that can be used to attach meta-data at the aspect-level.

This is useful for introspection, for example, using some key to filter out aspects or communicate information about aspects to other tooling like graph generators.

Since .meta is a freeformType you can add any custom attribute or import a module on it.

The following are default meta.* attributes defined by Den:

aspect.name # "igloo", the submodule name
aspect.meta.loc # the location [ "den" "aspects" "igloo" ] from where name is derived.
aspect.meta.name # "den.aspects.igloo" derived from loc
aspect.meta.file # the last file where this aspect was defined.
aspect.meta.self # a reference to the aspect module `config`.

You can access meta values by referencing an aspect:

den.aspects.igloo.meta.name

Or if an aspect needs to access its own meta data as part of its parametric functor, it can do something like:

den.aspects.foo =
{ config, lib, ... }: # module args to access `config.meta`
{
meta.foo = "bar";
__functor = self: ctx:
if config.meta.foo == "bar"
then { } # do nothing, skip.
{
includes = filter (i: someCondition i) self.includes;
};
}

In case an aspect needs a custom submodule, it can be added this way:

# User aspect `tux`
den.aspects.tux =
{ config, ... }: # The function-args style is required for `imports` not to be interpreted as an Aspect class.
{
imports = [
{
options = {
default-key = lib.mkOption {
type = with lib.types; str;
};
keygrip = lib.mkOption {
type = with lib.types; str;
};
};
}
];
default-key = "Hello World!";
keygrip = "Hello World!";
}

And then, you can refer to tux aspect from other aspects:

{ den, ... }:
{
den.aspects.igloo = {
homeManager =
{ config, ... }:
{
programs = {
gpg = {
enable = true;
settings.default-key = den.aspects.tux.default-key; # Access the `tux` aspect statically
};
};
services = {
gpg-agent = {
enable = true;
enableSshSupport = true;
sshKeys = [
den.aspects.${config.home.username}.keygrip # Access the current user aspect dynamically
];
};
};
};
};
}

Aspects are fixed-point, meaning that they can reference themselves, as such:

{
den.aspects.igloo =
{config,...}: # Here, `config` represents the aspect `igloo` itself.
{
meta = {
default-key = "Hello World!";
};
homeManager = {
programs = {
gpg = {
enable = true;
settings.default-key = config.meta.default-key;
};
git = {
enable = true;
settings.signing.key = config.meta.default-key;
}
};
};
};
}
Contribute Community Sponsor