Sponsor Vic Ask DeepWiki Dendritic Nix License CI Status
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 context transformation pipeline takes initially {host} # and traverses its topology (host->[users]->[homes]) aggregating deps aspect = den.ctx.host { host = den.hosts.x86_64-linux.my-laptop; }; # obtain the final module for nixos class 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 has Zero Dependencies. den.lib 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.
Den is a declarative data transformation pipeline. Infra entities are traversed via
den.ctxtransformations and configurations for them are generated whenden.aspectsare applied at each context stage.
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.
flake-parts-modules: Den forward classes for third-party perSystem submodules: nix-unit on aspects, mightyiam/files generation, devshells, etc.
example: cross-platform
ci: Each feature tested as code examples
bogus: Isolated test for bug reproduction
Want yours featured? send me a DM via matrix or zulip (links at GH Discussions)
@vic: Fleet sharing user, author spends more time in Den itself. (-flakes +npins +auto-update +ci)
@quasigod: Beautiful organization, uses custom Den namespaces and Den angle brackets (+flake-parts)
@Gwenodai: Clever organization with path-naming conventions, custom guarded and forwarding classes
@adda: Multiple hosts (+flake-parts +flake-file +home-manager +files)
Den is also being used on internal infra at The European Commission.
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 .#vic
These 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" "persistence" "/nix/persist/system" ]; fromAspect = _: den.aspects.${host.aspect}; guard = { options, config, ... }: options ? environment.persistence; }; # 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 ]; }; };