den - Aspect-oriented context-driven Dendritic Nix configurations.#
den and vic's dendritic libs made for you with Love++ and AI--. If you like my work, consider sponsoring
|
At its core, Den is a library built on flake-aspects for activating configuration-aspects via context-transformation pipelines. On top of the library, Den 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. Templates:#default: +flake-file +flake-parts +home-manager minimal: +flakes -flake-parts -home-manager noflake: -flakes +npins +lib.evalModules +nix-maid 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 Examples:#
GitHub Search: Growing community adoption, ❄️ Try it: |
Testimonials#
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
Den Versioning - v0.x is moving fast#
Den main always requires flake-aspects main. Same for latest versions.
# Bleeding edge Den
{
inputs.den.url = "github:vic/den";
inputs.flake-aspects.url = "github:vic/flake-aspects";
}
main branch follows development, this is where PRs are merged, it is stable in the sense
that every PR checks CI. However, some PR might introduce changes that require you being
aware of what is happening in Den. For people tracking development, we have announcements
tagged heads-up whenever this happens.
# Released Den - no longer gets updates until next release
{
inputs.den.url = "github:vic/den/latest";
inputs.flake-aspects.url = "github:vic/flake-aspects/latest";
}
latest tag always follows the latest created tag. This is intended for people that wishes
to wait for a release and reading all announcements at release notes. if you flake update, you
will only move between release points.
Diff of changes since latest release: latest...main
For other than main and latest versions, each release-notes documents the particular flake-aspects version needed by Den. This is because flake-aspects can be used independently of Den, and it does not follows Den version numbers because it is not as fast-paced as Den.
Code example (OS configuration domain)#
Defining hosts, users and homes.#
den.hosts.x86_64-linux.lap.users.vic = {};
den.hosts.aarch64-darwin.mac.users.vic = {};
den.homes.aarch64-darwin.vic = {};
$ nixos-rebuild switch --flake .#lap
$ darwin-rebuild switch --flake .#mac
$ home-manager switch --flake .#vic
Extensible Schemas for hosts, users and homes.#
# 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; };
};
Dendritic Multi-Platform Hosts#
# modules/my-laptop.nix
{ den, inputs, ... }: {
den.aspects.my-laptop = {
# 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.
homeManager.programs.vim.enable = true;
};
}
Multiple User Home Environments#
# modules/vic.nix
{ den, ... }: {
den.aspects.vic = {
# supports multiple home environments, eg: for migrating from homeManager.
homeManager = { pkgs, ... }: { };
hjem.files.".envrc".text = "use flake ~/hk/home";
maid.kconfig.settings.kwinrc.Desktops.Number = 3;
# user can contribute OS-configurations to any host it lives on
darwin.services.karabiner-elements.enable = true;
# 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 Dendritic Nix Classes#
Custom classes is how Den implements homeManager, hjem, wsl, microvm support. You can use the very same mechanism to create your own classes.
# 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 = {};
};
Guarded Forwarding Classes#
Forward guards allow feature-detection without mkIf/mkMerge cluttering.
Aspects can simply assign configurations into a class (here persys)
from any file, without any mkIf/mkMerge cluttering. The logic for
determining if the class takes effect is defined at a single place.
Example inspired by @Doc-Steve
# Aspects use the `persys` class without any conditional. And guard guarantees
# settings are applied **only** when impermanence module has been imported.
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;
User-defined Extensions to Den Framework.#
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 ]; };
};