Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs

+209 -80
+1
docs/astro.config.mjs
··· 69 69 { label: 'From Flake to Den', slug: 'guides/from-flake-to-den' }, 70 70 { label: 'Declare Hosts & Users', slug: 'guides/declare-hosts' }, 71 71 { label: 'Configure Aspects', slug: 'guides/configure-aspects' }, 72 + { label: 'Host<->User Bidirectionality', slug: 'guides/bidirectional' }, 72 73 { label: 'Custom Nix Classes', slug: 'guides/custom-classes' }, 73 74 { label: 'Homes Integration', slug: 'guides/home-manager' }, 74 75 { label: 'Use Batteries', slug: 'guides/batteries' },
+153
docs/src/content/docs/guides/bidirectional.mdx
··· 1 + --- 2 + title: Host<-> User Bidirectional Configurations 3 + description: How to configure bidirectional behavior in Den and how it differs from mutual-provider. 4 + --- 5 + 6 + import { Aside } from '@astrojs/starlight/components'; 7 + 8 + <Aside title="Source" icon="github"> 9 + [`context/user.nix`](https://github.com/vic/den/blob/main/modules/context/user.nix) · 10 + [`context/host.nix`](https://github.com/vic/den/blob/main/modules/context/host.nix) · 11 + [`bidirectional.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/bidirectional.nix) · 12 + [`mutual-provider.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/mutual-provider.nix) 13 + </Aside> 14 + 15 + 16 + ## Normal, non-bidirectional OS configuration 17 + 18 + Den framework is built around **context pipeline** transformations. 19 + In order to create a full OS configuration, everything starts with a host definition: 20 + 21 + ```nix "igloo" "tux" 22 + den.hostx.x86_64-linux.igloo.users.tux = {} 23 + ``` 24 + 25 + We need to build the `nixos` Nix module that will later be used by `lib.nixosSystem`. 26 + To do so, Den invokes the `den.ctx.host` pipeline like this: 27 + 28 + 29 + ```mermaid 30 + sequenceDiagram 31 + participant Den 32 + participant host as den.ctx.host 33 + participant user as den.ctx.user 34 + participant igloo as den.aspects.igloo 35 + participant tux as den.aspects.tux 36 + 37 + Den ->> host : {host = igloo} 38 + 39 + host ->> igloo : request nixos class 40 + igloo -->> igloo : each igloo.includes takes { host } 41 + igloo -->> host : { nixos = ... } owned and parametric results 42 + 43 + host ->> user : fan-outs for each user: { host, user } 44 + 45 + user ->> tux : request nixos class 46 + tux -->> tux : home classes forwarded as nixos class 47 + tux -->> tux : each tux.includes takes { host, user } 48 + tux -->> user : { nixos = ... } owned and parametric results 49 + 50 + user -->> host : { nixos = ... } all user contributions 51 + 52 + host -->> Den : complete nixos module for lib.nixosSystem 53 + 54 + ``` 55 + 56 + This is the normal NixOS pipeline an __Not Bidirectional__. All OS contributions come from 57 + the host itself and from each of its user. 58 + 59 + 60 + ## What Bidirectionality means 61 + 62 + __Bidirectionality__ means that not only a User contributes 63 + configuration to a Host, but **also** that a Host contributes 64 + configurations to a User. 65 + 66 + This is useful when the Host wishes to provide a 67 + commmon home environment for its users. 68 + 69 + ## `den.provides.bidirectional` 70 + 71 + Bidirectionality is enabled __per-user__ or for _all_ of them. 72 + 73 + ```nix 74 + # only tux takes configurations from its hosts 75 + den.aspects.tux.includes = [ den._.bidirectional ]; 76 + 77 + # for ALL users 78 + den.ctx.user.includes = [ den._.bidirectional ]; 79 + ``` 80 + 81 + When Bidirectionality is enabled, the interaction looks like this: 82 + 83 + ```mermaid 84 + sequenceDiagram 85 + participant Den 86 + participant host as den.ctx.host 87 + participant user as den.ctx.user 88 + participant igloo as den.aspects.igloo 89 + participant tux as den.aspects.tux 90 + 91 + Den ->> host : {host = igloo} 92 + 93 + host ->> igloo : request nixos class 94 + igloo -->> igloo : each igloo.includes takes { host } 95 + igloo -->> host : { nixos = ... } owned and parametric results 96 + 97 + host ->> user : fan-outs for each user: { host, user } 98 + 99 + user ->> tux : request nixos class 100 + 101 + tux ->> igloo : request home class 102 + igloo -->> igloo : each igloo.includes takes { host, user } 103 + igloo -->> tux : { hjem = ... } owned and parametric results 104 + 105 + tux -->> tux : home classes forwarded as nixos class 106 + 107 + tux -->> tux : each tux.includes takes { host, user } 108 + tux -->> user : { nixos = ... } owned and parametric results 109 + 110 + user -->> host : { nixos = ... } all user contributions 111 + 112 + host -->> Den : complete nixos module for lib.nixosSystem 113 + 114 + ``` 115 + 116 + Crucial points here are `igloo.includes takes { host }` and `igloo.includes takes { host, user }`. 117 + 118 + Because the list of aspects at `igloo.includes` get invoked twice, with different contexts, 119 + functions at `igloo.includes` must take care of the following: 120 + 121 + ```nix 122 + # use den.lib.take.exactly to avoid being called with `{host, user}` 123 + take.exactly ({ host }: ...) 124 + 125 + # use den.lib.take.atLeast to avoid being called with `{host}` 126 + take.atLeast ({ host, user }: ...) 127 + ``` 128 + 129 + Read the documentation at [`context/user.nix`](https://github.com/vic/den/blob/main/modules/context/user.nix) for all the details. 130 + 131 + ## `den.provides.mutual-provider` 132 + 133 + An alternative to bidirectionality is [`den.provides.mutual-provider`](https://github.com/vic/den/blob/main/modules/aspects/provides/mutual-provider.nix). 134 + 135 + This battery is more explicit, since it requires an explicit `.provides.` relationship between users and hosts. 136 + 137 + ```nix 138 + # Host provides to a particular user 139 + den.aspects.igloo.provides.tux = { 140 + hjem = ...; 141 + }; 142 + 143 + # User provides to a particular host 144 + den.aspects.tux.provides.igloo = { 145 + nixos = ...; 146 + }; 147 + ``` 148 + 149 + To enable it for both users and hosts, include at default: 150 + 151 + ```nix 152 + den.default.includes = [ den._.mutual-provider ]; 153 + ```
+42 -45
docs/src/content/docs/guides/configure-aspects.mdx
··· 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 8 <Aside title="Source" icon="github"> 9 - [`modules/aspects/`](https://github.com/vic/den/tree/main/modules/aspects) · Tests: [`features/user-host-bidirectional-config.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/user-host-bidirectional-config.nix) 9 + [`github:vic/flake-aspects`](https://github.com/vic/flake-aspects) · 10 + [`aspects definition`](https://github.com/vic/den/tree/main/modules/aspects/definition.nix) · 11 + [`example aspects`](https://github.com/vic/den/tree/main/modules/aspects/provides) · 10 12 </Aside> 11 13 12 14 # Den created aspects. 13 - 14 - An aspect is an attrset with per-class modules. 15 15 16 16 <Aside icon="nix" title="Nix Classes"> 17 17 18 - An flake-aspect (used by Den) is just an attrset that contains modules of different Nix `class`. 18 + A Dendritic aspect is an attrset that contains modules of different Nix `class`. 19 19 20 20 ```nix 21 - gaming = { nixos = ...; homeManager = ...; hjem = ...; } 21 + gaming = { nixos = ...; homeManager = ...; hjem = ...; darwin = ...; } 22 22 ``` 23 23 24 - Such an attrset is said to be [**Dendritic**](https://dendrix.oeiuwq.com/Dendritic.html) because it allows configuring a single cross-cutting concern (`gaming`) over different Nix classes. 24 + Such an attrset is said to be [**Dendritic**](https://dendrix.oeiuwq.com/Dendritic.html) because it allows configuring a single cross-cutting concern (`gaming`) over different configuration domains. 25 25 26 26 Nix classes are NOT an artificial concept created by Den. There are [many nix classes in the wild](https://github.com/search?q=language%3ANix+%28%22+class+%3D+%22+AND+%22evalModules%22%29&type=code), the most common are [`nixos`](https://github.com/search?q=repo%3ANixOS%2Fnixpkgs+%22class+%3D+%5C%22nixos%5C%22%22&type=code), [`darwin`](https://github.com/nix-darwin/nix-darwin/blob/da529ac9e46f25ed5616fd634079a5f3c579135f/eval-config.nix#L81), [`homeManager`](https://github.com/nix-community/home-manager/blob/5be5d8245cbc7bc0c09fbb5f38f23f223c543f85/nixos/common.nix#L27). 27 27 </Aside> ··· 58 58 59 59 ## Aspect Basics 60 60 61 - In addition to class modules, an aspect has an `includes` list referencing other aspects, and `provides` for sub-aspects in same category: 61 + In addition to per-class modules, an aspect has an `includes` list referencing other aspects, and `provides` for sub-aspects in same category: 62 62 63 63 ```nix 64 64 { den, ... }: { ··· 72 72 73 73 # Dependencies 74 74 includes = [ 75 - den.aspects.dev-tools 76 75 den.provides.primary-user 76 + den.aspects.gaming.provides.emulation 77 77 ]; 78 - 79 - # Named sub-aspects 80 - provides.gpu = { host, ... }: 81 - lib.optionalAttrs (host ? gpu && host.gpu == "nvidia") { 82 - nixos.hardware.nvidia.enable = true; 83 - }; 84 78 }; 79 + 80 + den.aspects.gaming = { 81 + darwin = ...; 82 + nixos = ...; 83 + provides.emulation = { 84 + darwin = ...; 85 + nixos = ...; 86 + }; 87 + } 85 88 } 86 89 ``` 87 90 88 91 ## Owned Configs 89 92 90 93 Attributes named after a Nix class (`nixos`, `darwin`, `homeManager`, or any 91 - custom class) are **owned configs**. They can be: 94 + custom class) are referred by this documentation as **owned configs**. 92 95 93 - - **Attrsets**: merged directly into the class configuration. 94 - - **Functions**: called with the class's module arguments (`{ config, pkgs, lib, ... }`). 96 + They are just a regular Nix module: 97 + 98 + - **Attrset Module**: plain and simple configuration. 99 + - **Function Module**: taking module arguments (`{ config, pkgs, lib, ... }: { }`). 95 100 96 101 ```nix 97 102 # Attrset form ··· 105 110 106 111 ## Includes 107 112 108 - `includes` is a list of aspects, attrsets, or parametric functions: 113 + `includes` is a list of aspects used to declare dependencies between aspects. 114 + 115 + Unlike other Nix libraries that use stringly-typed "tags" to define requirements, 116 + Den uses real aspect references, these can be type-checked by the Nix module system. 109 117 110 118 ```nix 111 119 den.aspects.workstation.includes = [ 112 120 # Reference another aspect (its full DAG is included) 113 121 den.aspects.dev-tools 122 + ]; 123 + ``` 114 124 115 - # Static attrset (always applied) 116 - { nixos.programs.vim.enable = true; } 125 + It is important to note the three kinds of values that Den distinguishes 126 + as part of an includes list: 117 127 118 - # Parametric function (applied when context shape matches) 119 - ({ host, ... }: { nixos.networking.hostName = host.hostName; }) 128 + 1. Static (plain attribute set): `{ nixos.foo = ...; }` 129 + 2. Static (flake-aspects' leaf): `{class, aspect-chain}: { ${class}.foo = ...; }` 130 + 3. Parametric (any other function): `{ host, user }: { ${host.class}.foo = ...; }` 120 131 121 - # Battery 122 - (den.provides.user-shell "fish") 123 - ]; 124 - ``` 132 + `(1)` and `(2)` are termed *static aspects* and are the terminal leafs of flake-aspects DAG. 133 + `(1)` provides configuration unconditionally, and `(2)` gets access to the `class` that is 134 + being resolved and an `aspect-chain` that lead to the current aspect (most recent last). 135 + 136 + 137 + `(3)` is a more interesting kind of aspect that is used by Den to pass host/user defitions 138 + into these functions, so they can inspect the host features and provide 139 + configuration accordingly. 140 + 125 141 126 142 ## Provides 127 143 ··· 169 185 Parametric functions in `den.default.includes` are evaluated at every context 170 186 stage. Use `den.lib.take.exactly` if a function should only run in specific contexts. 171 187 </Aside> 172 - 173 - ## Bidirectional Flow 174 - 175 - Users configure their hosts. Hosts configure their users. Aspects flow 176 - in both directions: 177 - 178 - ```nix 179 - # User aspect contributes to host's NixOS config 180 - den.aspects.alice = { 181 - nixos.users.users.alice.extraGroups = [ "docker" ]; 182 - homeManager = { pkgs, ... }: { home.packages = [ pkgs.htop ]; }; 183 - }; 184 - 185 - # Host aspect contributes to all its users' home config 186 - den.aspects.laptop = { 187 - homeManager.programs.ssh.enable = true; 188 - nixos.services.openssh.enable = true; 189 - }; 190 - ```
+13 -35
docs/src/content/docs/guides/declare-hosts.mdx
··· 7 7 8 8 9 9 <Aside title="Source" icon="github"> 10 - [`modules/_types.nix`](https://github.com/vic/den/blob/main/modules/_types.nix) 10 + [`types`](https://github.com/vic/den/blob/main/nix/types.nix) - 11 + [`schema`](https://github.com/vic/den/blob/main/modules/options.nix) - 12 + [`examples/microvm`](https://github.com/vic/den/blob/main/templates/microvm/modules/microvm-integration.nix) 11 13 </Aside> 12 14 13 15 ··· 28 30 ``` 29 31 30 32 Each host entry produces a configuration in `flake.nixosConfigurations` or 31 - `flake.darwinConfigurations` depending on its `class` (auto-detected from system). 33 + `flake.darwinConfigurations` depending on its `class` (auto-detected from the host platform). 32 34 33 35 ## Host Schema 34 36 35 37 <Aside title="Important" icon="nix"> 36 - Be sure to read about the entity [Schemas](/reference/schema/), each of `host`/`user`/`home` has a corresponding `den.schema.*` module for meta-configuration. Which can later be used in aspects that read from `host`. These schemas are the meta-data equivalent of what Dendritic flake-parts users do with flake-level options. 38 + Be sure to read about the entity [Schemas](/reference/schema/), each of `host`/`user`/`home` has a corresponding `den.schema.*` module for meta-configuration of entity features. Which can later be used in aspects that read from `host`. These schemas are the meta-data equivalent of what Dendritic flake-parts users do with flake-level options. 37 39 </Aside> 38 40 39 41 Hosts have these options (all with sensible defaults): ··· 53 55 54 56 ## User Declaration 55 57 56 - Users are declared under a host: 58 + Users are declared as part of a host: 57 59 58 60 ```nix 59 61 den.hosts.x86_64-linux.laptop = { ··· 102 104 103 105 ## Base Modules 104 106 105 - `den.schema.{host,user,home,conf}` provides shared configuration applied to all entities of each kind. 107 + `den.schema.{host,user,home}` provides shared configuration applied to all entities of each kind. 106 108 107 - Some batteries also extend base modules [see `hjem-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/hjem/hjem-os.nix#L50) which defines `hjem.module` option. 109 + Some batteries also extend base modules [see `home-env.nix`](https://github.com/vic/den/blob/main/nix/home-env.nix) 110 + that is used by home-manager/hjem/maid to extend the Host schema with options like `hjem.module`/`home-manager.module`. 111 + 112 + Another more advanced examples is our [templates/microvm](https://github.com/vic/den/tree/main/templates/microvm/modules/microvm-integration.nix) 113 + that adds options related to running virtualized OS. 108 114 109 115 ```nix 110 116 { 111 - # Can be used to config each host 117 + # Can be used to specify features of all host 112 118 den.schema.host.home-manager.enable = true; 113 119 114 120 # Can be used to add schema options with defaults ··· 122 128 }; 123 129 } 124 130 ``` 125 - 126 - ## Aspect Association 127 - 128 - Each host and user has an `aspect` name (defaults to the config `name`). 129 - 130 - Den **automatically* creates a **parametric** aspect for each host, user and home using their specified classes. 131 - 132 - ```nix 133 - { 134 - den.hosts.x86_64-linux.laptop = { 135 - class = "nixos"; # default derived from x86_64-linux 136 - users.alice = { classes = [ "homeManager" "hjem" ]; }; 137 - }; 138 - 139 - # Den creates the following aspects: 140 - 141 - den.aspects.laptop = parametric { 142 - nixos = {}; 143 - }; 144 - 145 - den.aspects.alice = parametric { 146 - homeManager = {}; 147 - hjem = {}; 148 - }; 149 - } 150 - ``` 151 - 152 - Later, when the context pipeline looks up `den.aspects.${aspect}` to find the configuration for each entity. 153 131 154 132 ## Freeform Schema 155 133