den and vic's dendritic libs made for you with Love++ and AI--. If you like my work, consider sponsoring
Den allows creating parametric configurations by taking the Dendritic pattern to the function-level.
These configurations become specific when applied to your particular infra entities (hosts/users), while allowing re-usable aspects to be shared between hosts, users, or across other flakes and non-flake projects.
# An aspect is a function taking context and
# returning modules of different Nix classes
den.aspects.gaming = { host, user }: {
nixos = { pkgs, ... }: ...;
darwin = ...;
hjem = ...;
homeManager = ...;
# aspects can depend on other aspects
includes = [ den.aspects.performance ];
# aspects can be organized in sub-aspects
provides.emulation = {
nixos = { pkgs, ... }: ... ;
};
} |
# These three lines is how Den instantiates a configuration.
# Other Nix configuration domains outside NixOS/nix-Darwin
# can use the same pattern. demo: templates/nvf-standalone
# A transformation pipeline takes initial context: {host}
# and traverses its topology (host->users->homes) aggregating deps
aspect = den.ctx.host { host = den.hosts.x86_64-linux.my-laptop; };
# flake-aspects API (re-exported by Den) resolves final NixOS module
nixosModule = den.lib.aspects.resolve "nixos" [ ] aspect;
# Use NixOS API to instantiate or mix-in with other custom modules
nixosConfigurations.my-laptop = lib.nixosConfiguration {
modules = [ nixosModule ];
}; |
Den library is built on flake-aspects and is domain agnostic, it can be used to configure anything Nix-configurable.
On top of den.lib, Den also provides a framework for the NixOS/nix-Darwin/Home-Manager Nix domains.
Den embraces your Nix choices and does not impose itself. All parts of Den are optional and replaceable. Works with your current setup, with/without flakes, flake-parts or any other Nix module system.
|
default: +flake-file +flake-parts +home-manager minimal: +flakes -flake-parts -home-manager noflake: -flakes +npins +lib.evalModules +nix-maid nvf-standalone: Standalone neovim apps, showcasing Den without NixOS/Darwin. microvm: MicroVM runnable-pkg and guests. custom ctx-pipeline. example: cross-platform ci: Each feature tested as code examples bogus: Isolated test for bug reproduction
Growing community adoption: Usage Search ❄️ Try it: # Run virtio MicroVM from templates/microvm
nix run github:vic/den?dir=templates/microvm#runnable-microvm# Run NVF-Standalone neovim from templates/nvf-standalone
nix run github:vic/den?dir=templates/nvf-standalone#my-neovim# Run qemu VM from templates/example
nix run github:vic/den |
Den takes the Dendritic pattern to a whole new level, and I cannot imagine going back.
—@adda- Very early Den adopter after using Dendritic flake-parts and Unify.
I’m super impressed with den so far, I’m excited to try out some new patterns that Unify couldn’t easily do.
—@quasigod- Author of Unify dendritic-framework, on adopting Den.
Massive work you did here!
—@drupol- Author of “Flipping the Configuration Matrix” Dendritic blog post.
Thanks for the awesome library and the support for non-flakes… it’s positively brilliant!. I really hope this gets wider adoption.
—@vczf- At#den-lib:matrix.orgchannel.
Den is a playground for some very advanced concepts. I’m convinced that some of its ideas will play a role in future Nix areas. In my opinion there are some raw diamonds in Den.
—@Doc-Steve- Author of Dendritic Design Guide
Simplest example, one-liner definitions.
den.hosts.x86_64-linux.lap.users.vic = {};
den.hosts.aarch64-darwin.mac.users.vic = {};
den.homes.aarch64-darwin."vic@mac" = {};The den.aspects.vic aspect is shared between
these two hosts and standalone home-manager.
The vic@mac homeConfiguration has osConfig = mac.config.
Activate with:
$ nixos-rebuild switch --flake .#lap
$ darwin-rebuild switch --flake .#mac
$ home-manager switch --flake .#vicThese allow meta-configuration on entities, akin to what Dendritic flake-parts users do with top-level options, but here scoped to each entity type.
People use this for declaring host or user capabilities that will later be used by aspects to implement configurations.
# extensible base modules for common, typed schemas
den.schema.user = { user, lib, ... }: {
config.classes =
if user.userName == "vic" then [ "hjem" "maid" ]
else lib.mkDefault [ "homeManager" ];
options.mainGroup = lib.mkOption { default = user.userName; };
};A single aspect like den.aspects.workstation can be
shared between (included-at) NixOS/nix-Darwin/WSL hosts.
Each aspect uses several Nix classes to define behaviour.
# modules/workstation.nix
{ den, inputs, ... }: {
den.aspects.workstation = {
# re-usable configuration aspects. Den batteries and yours.
includes = [ den.provides.hostname den.aspects.work-vpn ];
# regular nixos/darwin modules or any other Nix class
nixos = { pkgs, ... }: { imports = [ inputs.disko.nixosModules.disko ]; };
darwin = { pkgs, ... }: { imports = [ inputs.nix-homebrew.darwinModules.nix-homebrew ]; };
# Custom Nix classes. `os` applies to both nixos and darwin.
# Contributed by @Risa-G.
# See https://den.oeiuwq.com/guides/custom-classes/#user-contributed-examples
os = { pkgs, ... }: {
environment.systemPackages = [ pkgs.direnv ];
};
# host can contribute default home environments
# to all its users.
provides.to-users = {
homeManager = { pkgs, ... }: {
programs.vim.enable = true;
home.packages = [ pkgs.neovide ];
};
};
};
}Each user can define configurations for different home environments, aiding with migration from homeManager to hjem or others.
# modules/vic.nix
{ den, ... }: {
den.aspects.vic = {
# supports multiple home environments
homeManager = { pkgs, ... }: { };
hjem.files.".envrc".text = "use flake ~/hk/home";
maid.kconfig.settings.kwinrc.Desktops.Number = 3;
# user can contribute OS-configurations
# to all hosts it lives on
darwin.services.karabiner-elements.enable = true;
# user can specify config for specific host
provides.rog-tower = {
nixos = ...; # enable CUDA and gaming profile
};
# user class forwards into
# {nixos/darwin}.users.users.<userName>
user = { pkgs, ... }: {
packages = [ pkgs.helix ];
description = "oeiuwq";
};
includes = [
den.provides.primary-user # re-usable batteries
(den.provides.user-shell "fish") # parametric aspects
den.aspects.tiling-wm # your own aspects
den.aspects.gaming.provides.emulators
];
};
}Custom classes is how Den implements user, homeManager, hjem, wsl, microvm support. You can use the very same mechanism to create your own Nix classes.
The den.provides.forward battery is the core of it.
# Example: A class for role-based configuration between users and hosts
roleClass =
{ host, user }:
{ class, aspect-chain }:
den._.forward {
each = lib.intersectLists (host.roles or []) (user.roles or []);
fromClass = lib.id;
intoClass = _: host.class;
intoPath = _: [ ];
fromAspect = _: lib.head aspect-chain;
};
den.ctx.user.includes = [ roleClass ];
den.hosts.x86_64-linux.igloo = {
roles = [ "devops" "gaming" ];
users = {
alice.roles = [ "gaming" ];
bob.roles = [ "devops" ];
};
};
den.aspects.alice = {
# enabled when both support gaming role
gaming = { pkgs, ... }: { programs.steam.enable = true; };
};
den.aspects.bob = {
# enabled when both support devops role
devops = { pkgs, ... }: { virtualisation.podman.enable = true; };
# not enabled at igloo host (bob missing gaming role on that host)
gaming = {};
};Any module/file can contribute to any aspects directly
into their feature-concern Nix classes, without
having to deal with feature-detection or having
mkIf/mkMerge clutterring on all the codebase.
The logic (guard) for conditional inclusion of a forwarded-class configuration is defined at a single place.
This uses pkgs.stdenv.isXYZ to define hmXYZ classes,
because some hm configurations might be only available
on specific platforms.
# aspect `tux` is used on both platforms
den.hosts.x86_64-linux.igloo.users.tux = { };
den.hosts.aarch64-darwin.apple.users.tux = { };
den.aspects.hmPlatforms =
{ class, aspect-chain }:
den._.forward {
each = [ "Linux" "Darwin" ];
fromClass = platform: "hm${platform}";
intoClass = _: "homeManager";
intoPath = _: [ ];
fromAspect = _: lib.head aspect-chain;
guard = { pkgs, ... }: platform: lib.mkIf pkgs.stdenv."is${platform}";
adaptArgs = { config, ... }: { osConfig = config; };
};
den.aspects.tux = {
includes = [ den.aspects.hmPlatforms ];
hmDarwin = { pkgs, ... }: { home.packages = [ pkgs.iterm2 ]; };
hmLinux = { pkgs, ... }: { home.packages = [ pkgs.wl-clipboard-rs ]; };
};Modules define configurations at aspects using the
persys class directly, without any conditional.
The guard guarantees they are applied only when impermanence module is enabled at host.
Inspired by @Doc-Steve
persys = { host }: den._.forward {
each = lib.singleton true;
fromClass = _: "persys";
intoClass = _: host.class;
intoPath = _: [ "environment" "persistance" "/nix/persist/system" ];
fromAspect = _: den.aspects.${host.aspect};
guard = { options, config, ... }: options ? environment.persistance;
};
# enable on all hosts
den.ctx.host.includes = [ persys ];
# aspects just attach config to custom class
den.aspects.my-laptop.persys.hideMounts = true;See example template/microvm for an example
of custom den.ctx and den.schema extensions for supporting
Declarative MicroVM guests with automatic host-shared /nix/store.
den.hosts.x86_64-linux.guest = {};
den.hosts.x86_64-linux.host = {
microvm.guests = [ den.hosts.x86_64-linux.guest ];
};
den.aspects.guest = {
# propagated into host.nixos.microvm.vms.<name>;
microvm.autostart = true;
# guest supports all Den features.
includes = [ den.provides.hostname ];
# As MicroVM guest propagated into host.nixos.microvm.vms.<name>.config;
nixos = { pkgs, ... }: { environment.systemPackages = [ pkgs.hello ]; };
};