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

docs (#217)

authored by oeiuwq.com and committed by

GitHub 6d847540 628f19fe

+1715 -2288
-1
docs/astro.config.mjs
··· 31 31 { label: 'Motivation', slug: 'motivation', }, 32 32 { label: 'Community', slug: 'community' }, 33 33 { label: 'Contributing', slug: 'contributing' }, 34 - { label: 'Sponsor', slug: 'sponsor' }, 35 34 ], 36 35 }, 37 36 {
+37 -76
docs/src/content/docs/explanation/aspects.mdx
··· 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - 9 - <Aside title="Use the Source, Luke" icon="github"> 10 - [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · Built on [flake-aspects](https://github.com/vic/flake-aspects) 8 + <Aside title="Source" icon="github"> 9 + [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) -- 10 + Built on [flake-aspects](https://github.com/vic/flake-aspects) 11 11 </Aside> 12 12 13 - ## The __functor Pattern 13 + ## The `__functor` Pattern 14 14 15 - In Nix, any attribute set with `__functor` can be called as a function: 15 + Any Nix attrset with `__functor` can be called as a function: 16 16 17 17 ```nix 18 18 let ··· 23 23 in counter 8 # => 50 24 24 ``` 25 25 26 - The `__functor` receives `self` (the attrset) and an argument. 26 + The `__functor` receives `self` (the attrset itself) and returns a function. 27 27 28 28 ## Aspects as Functors 29 29 30 - Every aspect in `flake-aspects` has a default `__functor`: 30 + Every aspect in flake-aspects has a default `__functor` that ignores context 31 + and returns itself. Den replaces this functor with one that inspects context 32 + parameters to decide what to produce: 31 33 32 34 ```nix 33 - { 34 - nixos = { a = 1; }; 35 - __functor = self: _context: self; # ignores context 36 - } 37 - ``` 35 + # Default: returns self regardless of context 36 + { nixos.foo = 1; __functor = self: _ctx: self; } 38 37 39 - By default, it ignores context and returns itself. But you can replace 40 - `__functor` with one that **inspects** context: 41 - 42 - ```nix 43 - { 44 - nixos.foo = 24; 45 - __functor = self: context: 46 - if context ? host 47 - then self 48 - else { includes = [ fallback ]; }; 38 + # Parametric: inspects context shape 39 + den.lib.parametric { 40 + nixos.foo = 1; 41 + includes = [ 42 + ({ host }: { nixos.networking.hostName = host.hostName; }) 43 + ({ user }: { homeManager.home.username = user.userName; }) 44 + ]; 49 45 } 50 46 ``` 51 47 ··· 55 51 56 52 ### Owned Configurations 57 53 58 - Direct settings defined under a class name: 54 + Direct settings per Nix class: 59 55 60 56 ```nix 61 57 den.aspects.igloo = { ··· 67 63 68 64 ### Includes 69 65 70 - Dependencies on other aspects — a directed graph: 66 + A list forming a dependency graph: 71 67 72 68 ```nix 73 69 den.aspects.igloo.includes = [ 74 - # an static include 75 - { nixos.programs.vim.enable = true } 70 + # Static: applied unconditionally 71 + { nixos.programs.vim.enable = true; } 76 72 77 - # function includes 78 - # 79 - # activates on both { host, user }, { host } 80 - ({ host, ... }: { 81 - nixos.time.timeZone = "UTC"; 82 - }) 73 + # Parametric: applied when context matches argument shape 74 + ({ host, ... }: { nixos.time.timeZone = "UTC"; }) 83 75 84 - # activates only on { host } 85 - (den.lib.take.exactly ({ host }: { 86 - nixos.networking.hostName = host.hostName; 87 - })) 88 - 89 - # a reference to any other aspect 90 - den.aspects.tools._.editors 76 + # Reference to another aspect 77 + den.aspects.tools 91 78 ]; 92 79 ``` 93 80 94 - Includes can be: 95 - - **Static aspects** — always included 96 - - **Functions** — [Parametric Aspects](/explanation/parametric) called with context, included when they match 97 - 98 81 ### Provides 99 82 100 - Nested sub-aspects forming a tree: 83 + Named sub-aspects scoped to this aspect, accessible via `den.aspects.igloo._.name` 84 + or `den.aspects.igloo.provides.name`: 101 85 102 86 ```nix 103 - 104 - den.aspects.gaming.provides.emulation = { 105 - nixos.programs.retroarch.enable = true; 106 - }; 107 - 87 + den.aspects.igloo.provides.gpu = { host, ... }: 88 + lib.optionalAttrs (host ? gpu) { 89 + nixos.hardware.nvidia.enable = true; 90 + }; 108 91 ``` 109 92 110 93 ## Resolution 111 94 112 - When Den needs a NixOS module from an aspect, it calls flake-aspects API `.resolve`: 95 + Aspects are resolved for a specific class using the `resolve` API from flake-aspects: 113 96 114 97 ```nix 115 - module = den.aspects.igloo.resolve { 116 - class = "nixos"; 117 - }; 118 - ``` 119 - 120 - flake-aspects does the following to obtain the nixos module: 121 - 122 - ```mermaid 123 - graph TD 124 - Asp["den.aspects.igloo"] 125 - 126 - Asp --> Resolve[".resolve { class = 'nixos' }"] 127 - 128 - Resolve --> Owned["owned: nixos, darwin, homeManager"] 129 - 130 - Resolve --> Inc["includes: gaming, tools._.editors, fn..."] 131 - 132 - Owned --> Collect["Collect all 'nixos' attrs<br/>from aspect + transitive includes"] 133 - 134 - Inc --> Collect 135 - Collect --> Module["Single merged Nix module"] 136 - 137 - Collect --> Resolve 138 - 98 + module = den.aspects.igloo.resolve { class = "nixos"; }; 139 99 ``` 140 100 141 - This collects all `nixos` configs from the aspect and all its transitive 142 - includes into a single Nix module. 101 + Resolution collects all `nixos` attrs from owned configs, walks `includes` 102 + recursively, applies parametric dispatch, deduplicates, and produces a 103 + single `deferredModule` containing all NixOS configuration.
+68 -177
docs/src/content/docs/explanation/context-pipeline.mdx
··· 1 1 --- 2 - title: OS Context Pipeline 3 - description: The complete data flow from host declaration to final configuration. 2 + title: Context Pipeline 3 + description: How host/user/home declarations transform into NixOS/Darwin/HM configurations. 4 4 --- 5 5 6 - 7 - import { Aside } from '@astrojs/starlight/components'; 6 + import { Steps, Aside } from '@astrojs/starlight/components'; 8 7 9 - <Aside title="Use the Source, Luke" icon="github"> 10 - [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) · HM: [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix) · [`hm-integration.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-integration.nix) 8 + <Aside title="Source" icon="github"> 9 + [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) -- 10 + [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) -- 11 + [`modules/config.nix`](https://github.com/vic/den/blob/main/modules/config.nix) 11 12 </Aside> 12 13 13 - ## The NixOS/nix-Darwin Pipeline 14 + ## Pipeline Overview 14 15 15 - When Den evaluates a host configuration, data flows through a pipeline 16 - of context transformations. Here is the complete picture: 16 + When Den evaluates a host, it walks a pipeline of context transformations: 17 17 18 18 ```mermaid 19 - graph TD 20 - Host["den.hosts.x86_64-linux.igloo"] 21 - Host -->|"initial context"| CtxHost["ctx.host { host }"] 22 - 23 - CtxHost -->|"_.host"| HA["den.aspects.igloo<br/>(fixedTo { host })"] 24 - CtxHost -->|"into.default"| CtxDef1["<b>ctx.default</b> { host }"] 25 - CtxHost -->|"into.user<br/>(per user)"| CtxUser["ctx.user { host, user }"] 26 - CtxHost -->|"into.hm-host<br/>(if HM detected)"| CtxHM["ctx.hm-host { host }"] 27 - 28 - CtxUser -->|"_.user (self)"| UA["den.aspects.tux<br/>(fixedTo { host, user })"] 29 - CtxHost -->|"_.user (cross)"| XP["host aspect includes<br/>matching { host, user }"] 30 - CtxUser -->|"into.default"| CtxDef2["<b>ctx.default</b> { host, user }"] 31 - 32 - CtxHM -->|"_.hm-host"| HMmod["Import HM module"] 33 - CtxHM -->|"into.hm-user<br/>(per HM user)"| CtxHMU["ctx.hm-user { host, user }"] 34 - 35 - CtxHMU -->|"forward homeManager<br/>into host"| FW["home-manager.users.tux"] 36 - 37 - CtxDef1 -->|"includes"| DI1["<b>den.default.{class}</b> ()<br/>den.default.includes<br/>(host-context funcs)"] 38 - CtxDef2 -->|"includes"| DI2["den.default.includes<br/>(user-context funcs)"] 19 + flowchart TD 20 + start["den.hosts.x86_64-linux.laptop"] --> host["den.ctx.host {host}"] 21 + host -->|"_.host"| owned["Owned config: fixedTo {host} aspects.laptop"] 22 + host -->|"_.user"| userctx["atLeast aspects.laptop {host, user}"] 23 + host -->|"into.user"| user["den.ctx.user {host, user} (per user)"] 24 + user -->|"_.user"| userown["fixedTo {host,user} aspects.alice"] 25 + host -->|"into.hm-host"| hmhost["den.ctx.hm-host {host}"] 26 + hmhost -->|"import HM module"| hmmod["home-manager OS module"] 27 + hmhost -->|"into.hm-user"| hmuser["den.ctx.hm-user {host, user}"] 28 + hmuser -->|"forward homeManager class"| fwd["home-manager.users.alice"] 39 29 ``` 40 30 41 - ## Stage by Stage 31 + <Steps> 32 + 1. Host Context 42 33 43 - ### 1. Host Entry 34 + For each entry in `den.hosts.<system>.<name>`, Den creates a `{host}` context. 35 + The host context type (`den.ctx.host`) contributes: 44 36 45 - Den reads `den.hosts.x86_64-linux.igloo` and applies the initial context `den.ctx.host`: 37 + - `_.host` -- Applies the host's own aspect with `fixedTo { host }`, making 38 + all owned configs available for the host's class. 39 + - `_.user` -- For each user, applies the host's aspect with `atLeast { host, user }`, 40 + activating parametric includes that need both host and user. 46 41 47 - ```nix 48 - den.ctx.host { host = den.hosts.x86_64-linux.igloo; } 49 - ``` 42 + 2. User Context 50 43 51 - ### 2. Host Aspect Resolution 44 + `into.user` maps each `host.users` entry into a `{host, user}` context. 45 + The user context type (`den.ctx.user`) contributes: 52 46 53 - `den.ctx.host.provides.host` (aliased as `_.host`) locates `den.aspects.igloo` 54 - and fixes it to the host context. All owned configs and static includes from the 55 - host aspect are collected. 47 + - `_.user` -- Applies the user's own aspect with `fixedTo { host, user }`. 56 48 57 - ### 3. Default configs (host-level) 49 + 3. Derived Contexts 58 50 59 - `den.ctx.host.into.default` produces `{ host }` for `den.ctx.default`, which 60 - activates `den.default.includes` functions matching `{ host, ... }`. 51 + Batteries register additional `into.*` transformations on the host context: 61 52 62 - ### 4. User Enumeration 53 + | Transition | Condition | Produces | 54 + |---|---|---| 55 + | `into.hm-host` | `host.home-manager.enable && hasHmUsers` | `{host}` hm-host | 56 + | `into.hm-user` | Per HM-class user on hm-host | `{host, user}` hm-user | 57 + | `into.wsl-host` | `host.class == "nixos" && host.wsl.enable` | `{host}` wsl-host | 58 + | `into.hjem-host` | `host.hjem.enable && hasHjemUsers` | `{host}` hjem-host | 59 + | `into.hjem-user` | Per hjem-class user | `{host, user}` hjem-user | 60 + | `into.maid-host` | `host.nix-maid.enable && hasMaidUsers` | `{host}` maid-host | 61 + | `into.maid-user` | Per maid-class user | `{host, user}` maid-user | 63 62 64 - `den.ctx.host.into.user` maps over `host.users`, producing one 65 - `den.ctx.user { host, user }` per user. 63 + Each derived context can contribute its own aspect definitions and import 64 + the necessary OS-level modules (e.g., `home-manager.nixosModules.home-manager`). 66 65 67 - ### 5. User Aspect Resolution 66 + 3. Deduplication 68 67 69 - Two providers contribute to user context: 70 - - `den.ctx.user.provides.user` (self-provider) — locates the user's own aspect (`den.aspects.tux`) 71 - - `den.ctx.host.provides.user` (cross-provider) — the host's contribution, dispatching host aspect includes matching `{ host, user }` 68 + `dedupIncludes` in `modules/context/types.nix` ensures: 72 69 73 - ### 6. Default configs (user-level) 70 + - **First occurrence** of a context type uses `parametric.fixedTo`, which includes 71 + owned configs + statics + parametric matches. 72 + - **Subsequent occurrences** use `parametric.atLeast`, which only includes 73 + parametric matches (owned/statics already applied). 74 74 75 - `ctx.user.into.default` activates `den.default.includes` again, this time 76 - with `{ host, user }` — functions needing user context now match. 77 - Because `den.ctx.default` was already visited in stage 3, only parametric 78 - includes are dispatched (owned and static configs are **deduplicated**). 75 + This prevents `den.default` configs from being applied twice when the same 76 + aspect appears at multiple pipeline stages. 79 77 80 - ### 7. Home-Manager Detection 78 + 4. Home Configurations 81 79 82 - `den.ctx.host.into.hm-host` checks if the host has users with `"homeManager"` 83 - in their `classes` list and a supported OS. If so, it activates `den.ctx.hm-host`. 80 + Standalone `den.homes` entries go through a separate path: 84 81 85 - ### 8. HM Module Import (host-level) 82 + ```mermaid 83 + flowchart TD 84 + home["den.homes.x86_64-linux.alice"] --> homectx["den.ctx.home {home}"] 85 + homectx --> resolve["fixedTo {home} aspects.alice"] 86 + resolve --> hmc["homeConfigurations.alice"] 87 + ``` 86 88 87 - `den.ctx.hm-host.provides.hm-host` imports the Home-Manager NixOS/Darwin module. 89 + Home contexts have no host, so functions requiring `{ host }` are not activated. 90 + Functions requiring `{ home }` run instead. 88 91 89 - ### 9. HM User config collection (user-level) 92 + 5. Output 90 93 91 - For each HM user, `ctx.hm-user` uses `den._.forward` to take 92 - `homeManager` class configs and insert them into 93 - `home-manager.users.<name>` on the host. 94 - 95 - ## Home-Manager Detection Criteria 96 - 97 - `ctx.host.into.hm-host` does not always activate. It checks three conditions 98 - (see [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix)): 99 - 100 - 1. **OS class is supported** — the host's class is `nixos` or `darwin` 101 - 2. **HM users exist** — at least one user has `"homeManager"` in their `classes` 102 - 3. **HM module available** — `inputs.home-manager` exists, or the host has a custom `hm-module` 103 - 104 - All three must be true. Hosts without users, or with only non-HM users, skip the entire HM pipeline. 105 - 106 - ## Automatic Deduplication 107 - 108 - `den.default` (alias for `den.ctx.default`) is reached at **`{host}`**, **`{host, user}`**, 109 - and **`{home}`** context stages — once for the host context and once per user. 110 - 111 - Den's `ctxApply` automatically deduplicates includes using a seen-set: 112 - 113 - - **First visit** to a context type: includes **owned + static + parametric** configs via `fixedTo` 114 - - **Subsequent visits**: includes only **parametric** configs via `atLeast` 115 - 116 - Additionally, for each context, two providers are called: 117 - - **Self-provider** (`provides.${name}`) — the target's own aspect lookup 118 - - **Cross-provider** (`source.provides.${target}`) — the source's contribution (if defined) 119 - 120 - ```mermaid 121 - graph TD 122 - H["ctx.host { host }"] -->|"into.default (1st visit)"| D1["ctx.default { host }<br/><b>fixedTo</b>: owned + statics + parametric<br/>+ self-provider + cross-provider"] 123 - H -->|"into.user"| U["ctx.user { host, user }"] 124 - U -->|"into.default (2nd visit)"| D2["ctx.default { host, user }<br/><b>atLeast</b>: parametric only<br/>+ self-provider + cross-provider"] 125 - ``` 126 - 127 - This means: 128 - 129 - - **Owned** configs and **static** includes from `den.default` appear **exactly once** 130 - - **Parametric** functions in `den.default.includes` still run at every stage 131 - (each may match different context shapes) 132 - - Options of `types.package` or `types.listOf` no longer get duplicate entries 133 - 134 - <Aside> 135 - For parametric includes in `den.default.includes`, use `den.lib.take.exactly` 136 - to restrict which context stages a function matches: 137 - 138 - ```nix 139 - den.default.includes = [ den.aspects.foo ]; 140 - den.aspects.foo = take.exactly ({ host }: { nixos.x = 1; }); 141 - ``` 142 - 143 - This function runs only with `{ host }` context, not with `{ host, user }`. 144 - </Aside> 145 - 146 - ## Standalone Home-Manager 147 - 148 - For `den.ctx.home`, the pipeline is shorter: 149 - 150 - ```mermaid 151 - graph TD 152 - Home["den.homes.x86_64-linux.tux"] 153 - Home -->|"creates"| CtxHome["ctx.home { home }"] 154 - CtxHome -->|"_.home"| HomeA["den.aspects.tux<br/>(fixedTo { home })"] 155 - CtxHome -->|"into.default"| CtxDef["ctx.default { home }"] 156 - ``` 157 - 158 - ## den.default Is an Alias 159 - 160 - `den.default` is an alias for `den.ctx.default`. When you write: 94 + `modules/config.nix` collects all hosts and homes, calls `host.instantiate` 95 + (defaults to `lib.nixosSystem`, `darwinSystem`, or `homeManagerConfiguration` 96 + depending on class), and places results into `flake.nixosConfigurations`, 97 + `flake.darwinConfigurations`, or `flake.homeConfigurations`. 161 98 162 - ```nix 163 - den.default.homeManager.home.stateVersion = "25.11"; 164 - den.default.includes = [ den._.define-user ]; 165 - ``` 166 - 167 - You are actually setting `den.ctx.default.homeManager...` and 168 - `den.ctx.default.includes`. This means `den.default` is a full 169 - context type — it has self-named providers, `into`, `includes`, and owned attributes. 170 - 171 - ### How den.default Receives Data 172 - 173 - `host`, `user`, and `home` aspects **do not** include `den.default` directly. 174 - Instead, each context type transforms **into** `default`: 175 - 176 - ```nix 177 - den.ctx.host.into.default = lib.singleton; # passes { host } 178 - den.ctx.user.into.default = lib.singleton; # passes { host, user } 179 - den.ctx.home.into.default = lib.singleton; # passes { home } 180 - ``` 181 - 182 - This means `den.default` is reached through the declarative context 183 - pipeline, not by direct inclusion. The data flowing into `den.default` 184 - is whatever the source context provides. 185 - 186 - ### Best Practices 187 - 188 - `den.default` is useful for global settings like `home.stateVersion`. 189 - However, prefer attaching parametric includes to the appropriate 190 - context type instead: 191 - 192 - | Instead of | Use | 193 - |-----------|-----| 194 - | `den.default.includes = [ hostFunc ]` | `den.ctx.host.includes = [ hostFunc ]` | 195 - | `den.default.includes = [ hmFunc ]` | `den.ctx.hm-host.includes = [ hmFunc ]` | 196 - | `den.default.nixos.x = 1` | `den.ctx.host.nixos.x = 1` | 197 - 198 - ## Why This Design? 199 - 200 - Each context type is **independent** and **composable**. You can: 201 - 202 - - Add new context types without modifying existing ones 203 - - Attach aspects to any stage of the pipeline 204 - - Create custom transformations for domain-specific needs 205 - - Override any built-in context behavior 206 - 207 - The pipeline is not hardcoded — it's declared through `den.ctx` definitions 208 - that you can inspect, extend, and customize. 99 + </Steps>
+57 -196
docs/src/content/docs/explanation/context-system.mdx
··· 1 1 --- 2 2 title: Context System 3 - description: How den.ctx defines, transforms, and propagates context. 3 + description: How den.ctx types define data flow between entities. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - 9 - <Aside title="Use the Source, Luke" icon="github"> 10 - [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) 8 + <Aside title="Source" icon="github"> 9 + [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) -- 10 + [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) 11 11 </Aside> 12 12 13 - ## What Is a Context? 13 + ## What is a Context 14 14 15 - In Den, a **context** is an attribute set whose **names** (not values) determine 16 - which functions get called. When Den applies a context `{ host, user }` to a 17 - function `{ host, ... }: ...`, the function matches. A function `{ never }: ...` 18 - does not match and is ignored. 15 + A **context** is a named stage in Den's evaluation pipeline. Each context type is declared 16 + under `den.ctx.<name>` and carries: 19 17 20 - ## Why Named Contexts? 18 + - **Aspect definitions** (`provides.<name>`) -- what this context contributes to the resolved aspect. 19 + - **Transformations** (`into.<other>`) -- functions that produce new contexts from the current one. 20 + - **Provides** (`provides.<other>`) -- cross-context providers injected by other context definitions. 21 21 22 - Named contexts `ctx.host { host }` and `ctx.hm-host { host }` 23 - hold the same data, but `hm-host` **guarantees** that home-manager support was 24 - validated. This follows the [**parse-don't-validate**](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) principle: you cannot 25 - obtain an `hm-host` context unless all detection criteria passed. 22 + ## Built-in Context Types 26 23 27 24 ```mermaid 28 - graph LR 29 - H["den.ctx.host {host}"] -->|"hm-detect"| Check{"host OS supported by HM?<br/>host has HM users?<br/>inputs.home-manager exists?"} 30 - Check -->|"all true"| HMH["same data {host}<br/>as den.ctx.hm-host"] 31 - Check -->|"any false"| Skip["∅ skipped"] 32 - HMH -->|"guaranteed"| Use["HM pipeline<br/>proceeds safely"] 25 + flowchart TD 26 + host["{host}"] -->|"into.user (per user)"| user["{host, user}"] 27 + host -->|"into.hm-host (if HM enabled)"| hmhost["{host} hm-host"] 28 + hmhost -->|"into.hm-user (per HM user)"| hmuser["{host, user} hm-user"] 29 + host -->|"into.wsl-host (if WSL enabled)"| wslhost["{host} wsl-host"] 30 + host -->|"into.hjem-host (if hjem enabled)"| hjemhost["{host} hjem-host"] 31 + hjemhost -->|"into.hjem-user"| hjemuser["{host, user}"] 32 + host -->|"into.maid-host (if maid enabled)"| maidhost["{host} maid-host"] 33 + maidhost -->|"into.maid-user"| maiduser["{host, user}"] 33 34 ``` 34 35 35 - ## Context Types: den.ctx 36 + The framework defines these contexts in `modules/context/os.nix` and the various battery modules. 37 + Each battery (Home Manager, hjem, nix-maid, WSL) registers its own `into.*` transitions on the `host` context. 36 38 37 - Each context type is defined in `den.ctx`. Context types are 38 - [aspect submodules](/explanation/aspects/) — they inherit `includes`, `provides`, 39 - `description`, and owned freeform configs from `flake-aspects`'s `aspect` type. 40 - **Den adds only the `into` option for context transformations** 39 + ## Context Type Anatomy 40 + 41 + A context type at `den.ctx.host`: 41 42 42 43 ```nix 43 - den.ctx.foobar = { 44 - description = "The {foo, bar} context"; 44 + den.ctx.host = { 45 + description = "OS"; 45 46 46 - # the aspect responsible for configuring the foobar context 47 - provides.foobar = { foo, bar }: den.aspects.${foo}._.${bar}; 47 + # The main aspect activated by this context 48 + provides.host = { host }: den.aspects.${host.aspect}; 48 49 49 - # foobar contributions to the baz context 50 - provides.baz = { baz }: { }; 50 + # How this context contributes an aspect to other derived contexts 51 + provides.user = { host, user }: den.aspects.other-aspect; 51 52 52 - # cutting-point for including other aspects 53 - includes = [ /* parametric aspects */ ]; 54 - 55 - # context transformations 56 - into = { 57 - # how to produce baz context from a foobar one. 58 - baz = { foo, bar }: [{ baz = computeBaz foo bar; }]; 59 - }; 53 + # How to derive other contexts from this one 54 + into.user = { host }: 55 + map (user: { inherit host user; }) (lib.attrValues host.users); 60 56 }; 61 57 ``` 62 58 59 + The `_` (or `provides`) attrset maps context names to functions that take the current 60 + context data and return aspect fragments. The `into` attrset maps to functions that 61 + produce lists of new context values. 63 62 64 - | Component | Purpose | 65 - |-----------|---------| 66 - | `description` | Human-readable description | 67 - | `provides.${name}` | Self-named provider: given a context, find aspect responsible for configuration | 68 - | `provides.${target}` | Cross-provider: source's contribution to a target context (called per transformation) | 69 - | `includes` | Parametric aspects activated for this context (aspect cutting-point) | 70 - | `into` | Transformations fan-out into other context types | 63 + ## Context Resolution 71 64 72 - ## Context Application 65 + When Den processes a host, it calls `den.ctx.host { host = ...; }`. This triggers: 73 66 74 - A context type is callable — it's a functor: 67 + 1. `collectPairs` walks the context type's `into.*` transitions recursively, 68 + building a list of `{ ctx, ctxDef, source }` pairs. 69 + 2. `dedupIncludes` processes these pairs, applying `parametric.fixedTo` for the 70 + first occurrence of each context type and `parametric.atLeast` for subsequent 71 + ones, preventing duplicate owned configs. 72 + 3. The result is a flat list of aspect fragments merged into one `deferredModule`. 75 73 76 - ```nix 77 - aspect = den.ctx.foobar { foo = "hello"; bar = "world"; }; 78 - ``` 74 + ## Custom Contexts 79 75 80 - When applied, `ctxApply` walks the full `into` graph depth-first, 81 - collecting `(source, ctxDef, ctx)` triples, then deduplicates: 82 - 83 - 1. **First visit** to a context type — `fixedTo`: owned + static + parametric dispatch 84 - 2. **Subsequent visits** — `atLeast`: parametric dispatch only, **user must take care** 85 - 3. **Self-named provider** (`provides.${name}`) is always called when context is used 86 - 4. **Cross-provider** (`source.provides.${target}`) is called if the source defines one 87 - 88 - ```mermaid 89 - graph TD 90 - Apply["ctx.foobar { foo, bar }"] 91 - Apply --> Collect["collectPairs: walk into graph"] 92 - Collect --> Dedup["dedupIncludes: seen-set"] 93 - Dedup --> First["1st visit: fixedTo + self + cross"] 94 - Dedup --> Later["Nth visit: atLeast + self + cross"] 95 - First --> Result["merged includes list"] 96 - Later --> Result 97 - ``` 98 - 99 - ## Transformation Types 100 - 101 - Transformations have the type `source → [ target ]` — they return a **list**. 102 - This enables two patterns: 103 - 104 - ```mermaid 105 - graph TD 106 - subgraph "Fan-out (one → many)" 107 - Host1["{host}"] -->|"into.user"| U1["{host, user₁}"] 108 - Host1 -->|"into.user"| U2["{host, user₂}"] 109 - Host1 -->|"into.user"| U3["{host, user₃}"] 110 - end 111 - subgraph "Conditional (one → zero or one)" 112 - Host2["{host}"] -->|"into.hm-host"| Gate{"detection<br/>gate"} 113 - Gate -->|"passes"| HM["{host} as hm-host"] 114 - Gate -->|"fails"| Empty["∅ empty list"] 115 - end 116 - ``` 117 - 118 - **Fan-out** — one context producing many: 76 + You can define your own alternative context piplelines outside of `den.ctx.host` or 77 + create custom context types for domain-specific needs like cloud infrastructure: 119 78 120 79 ```nix 121 - den.ctx.host.into.user = { host }: 122 - map (user: { inherit host user; }) (attrValues host.users); 123 - ``` 124 - 125 - One host fans out to N user contexts. 126 - 127 - **Conditional propagation** — zero or one: 128 - 129 - ```nix 130 - den.ctx.host.into.hm-host = { host }: 131 - lib.optional (isHmSupported host) { inherit host; }; 132 - ``` 133 - 134 - If the condition fails, the list is empty and no `hm-host` context is created. 135 - The data is the same `{ host }`, but the named context guarantees the validation 136 - passed. 137 - 138 - ## Contexts as Aspect Cutting-Points 139 - 140 - Since context types **are** aspect submodules, they naturally have owned 141 - configs and `.includes`: 142 - 143 - ```nix 144 - den.ctx.hm-host.nixos.home-manager.useGlobalPkgs = true; 145 - 146 - den.ctx.hm-host.includes = [ 147 - ({ host, ... }: { nixos.home-manager.backupFileExtension = "bak"; }) 148 - ]; 149 - ``` 150 - 151 - This is like `den.default.includes` **but scoped** — it only activates for 152 - hosts with validated home-manager support. Use context includes to attach 153 - aspects to specific pipeline stages instead of the catch-all `den.default`. 154 - 155 - ## Extending Context Flow 156 - 157 - You can define new context types or new transformations into existing contexts from any module: 158 - 159 - ```nix 160 - den.ctx.hm-host.into.foo = { host }: [ { foo = host.name; } ]; 161 - den.ctx.foo._.foo = { foo }: { funny.names = [ foo ]; }; 162 - ``` 163 - 164 - The module system merges these definitions. You can extend the pipeline 165 - without modifying any built-in file. 166 - 167 - ## Built-in Context Types 168 - 169 - Den defines these context types for its NixOS/Darwin/HM framework: 170 - 171 - ### den.ctx.host — `{ host }` 172 - 173 - This is how NixOS configurations get created: 174 - 175 - ```nix 176 - # use den API to apply the context to data 177 - aspect = den.ctx.host { 178 - # value is `den.hosts.<system>.<name>`. 179 - host = den.hosts.x86_64-linux.igloo; 80 + den.ctx.my-service = { 81 + description = "Custom service context"; 82 + provides.my-service = den.aspects.my-service; 180 83 }; 181 84 182 - # use flake-aspects API to resolve nixos module 183 - nixosModule = aspect.resolve { class = "nixos"; }; 85 + den.ctx.host.into.my-service = { host }: 86 + lib.optional host.my-service.enable { inherit host; }; 184 87 185 - # use NixOS API to build the system 186 - nixosConfigurations.igloo = lib.nixosSystem { 187 - modules = [ nixosModule ]; 88 + den.aspects.my-service = { host }: { 89 + nixos.services.my-service.hostName = host.hostName; 188 90 }; 189 - ``` 190 91 191 - ### den.ctx.user — `{ host, user }` 192 92 193 - A host fan-outs a new context for each user. 194 93 195 - ### den.ctx.default 196 - 197 - Aliased as `den.default`, used to define static global settings for hosts, users and homes. 198 - 199 - ### den.ctx.hm-host — `{ host }` 200 - 201 - Home Manager enabled host. 202 - 203 - A den.ctx.host gets transformed into den.ctx.hm-host only if the [host supports home-manager](/explanation/context-pipeline/). 204 - 205 - When activated, it imports the HM module. 206 - 207 - ### den.ctx.hm-user — `{ host, user }` 208 - 209 - For each user that has its class value set to homeManager. 210 - 211 - When activated enables the `homeManager` user configuration class. 212 - 213 - ### den.ctx.home — `{ home }` 214 - 215 - Entry point for standalone Home-Manager configurations. 216 - 217 - ## Custom Context Types 218 - 219 - Create your own for domain-specific pipelines: 220 - 221 - ```nix 222 - den.ctx.greeting._.greeting = { hello }: 223 - { funny.names = [ hello ]; }; 224 - 225 - den.ctx.greeting.into.shout = { hello }: 226 - [{ shout = lib.toUpper hello; }]; 227 - 228 - den.ctx.shout._.shout = { shout }: 229 - { funny.names = [ shout ]; }; 230 94 ``` 231 95 232 - Applying `den.ctx.greeting { hello = "world"; }` produces both 233 - `"world"` and `"WORLD"` through the transformation chain. 234 - 235 - See the [Context Pipeline](/explanation/context-pipeline/) for the complete data flow. 236 - See the [`den.ctx` Reference](/reference/ctx/) for all built-in types. 96 + This creates a new stage in the pipeline that only activates for hosts 97 + with `my-service.enable = true`, and contributes NixOS config.
+62 -58
docs/src/content/docs/explanation/core-principles.mdx
··· 1 1 --- 2 2 title: Core Principles 3 - description: The two fundamental ideas that make Den unique. 3 + description: The two design principles behind Den. 4 4 --- 5 5 6 - ## The Two Pillars 6 + import { Aside } from '@astrojs/starlight/components'; 7 + 8 + ## Feature-First, Not Host-First 9 + 10 + <Aside title="Recommended Read"> 11 + [Flipping the Configuration Matrix](https://not-a-number.io/2025/refactoring-my-infrastructure-as-code-configurations/#flipping-the-configuration-matrix) by [Pol Dellaiera](https://github.com/drupol) was very influential in Den design and is a very recommended read. 7 12 8 - Den is built on two intertwined principles that together enable a new way 9 - of thinking about Nix configurations: 13 + See also my [dendrix article](https://dendrix.oeiuwq.com/Dendritic.html) about the advantages of Dendritic Nix. 14 + </Aside> 15 + 16 + Traditional Nix configurations start from hosts and push modules downward. 17 + Den follows a Dendritic model that inverts this: **aspects** (features) are the primary organizational unit. 18 + Each aspect declares its behavior per Nix class, and hosts simply select which 19 + aspects apply to them. 10 20 11 21 ```mermaid 12 - graph LR 13 - subgraph "1. Context Transformation" 14 - CT["Declarative context shapes"] --> TP["Pipeline of context transformations"] 22 + flowchart BT 23 + subgraph "Aspect: bluetooth" 24 + nixos["nixos: hardware.bluetooth.enable = true"] 25 + hm["homeManager: services.blueman-applet.enable = true"] 15 26 end 16 - subgraph "2. Context-Aware Aspects" 17 - CA["Functions of context"] --> CP["Provide Conditional Configs"] 18 - end 19 - CT -.-> CA 20 - CA -.-> CT 27 + nixos --> laptop 28 + nixos --> desktop 29 + hm --> laptop 30 + hm --> desktop 21 31 ``` 22 32 23 - ### 1. Context Transformation 33 + An aspect consolidates all class-specific configuration for a single concern. 34 + Adding bluetooth to a new host is one line: include the aspect. 35 + Removing it is deleting that line. 24 36 25 - Traditional Nix setups wire modules directly: this NixOS module imports 26 - that file, this Home-Manager config hardcodes that username. 27 - 28 - Den replaces this with a **declarative pipeline**. You declare entities 29 - (hosts, users, homes) and context types (shapes holding them). Den transforms these through 30 - a graph of context stages, each producing richer or conditioned data. 37 + ## Context-Driven Dispatch 31 38 32 - **the flow of data is declared separately from the configurations it produces**. 39 + Den uses function **parametric dispatch**: 40 + aspect functions declare which context parameters they need via their 41 + argument pattern. 33 42 34 - ### 2. Context-Aware Aspects 43 + ```nix 44 + # Runs in every context (host, user, home) 45 + { nixos.networking.firewall.enable = true; } 35 46 36 - Aspects are not just bundles of configs — they are **functions** that inspect 37 - context to decide what to produce. A function taking `{ host, user }` naturally 38 - adapts to every host-user combination. A function requiring `{ other }` is 39 - not used. 47 + # Runs only when a {host} context exists 48 + ({ host, ... }: { nixos.networking.hostName = host.hostName; }) 40 49 41 - **the shape of the function arguments determines when configs are produced, without explicit conditionals**. 50 + # Runs only when both {host, user} are present 51 + ({ host, user, ... }: { 52 + nixos.users.users.${user.userName}.extraGroups = [ "wheel" ]; 53 + }) 54 + ``` 42 55 43 - ## Why This Matters 56 + Den introspects function arguments at evaluation time. A function requiring 57 + `{ host, user }` is silently skipped in contexts that only have `{ host }`. 58 + No conditionals, no `mkIf`, no `enable` -- the context shape **is** the condition. 44 59 45 - ### Re-usability 60 + ## Composition via Includes 46 61 47 - An aspect like [`define-user`](/reference/batteries/#define-user) works on NixOS, Darwin, and standalone 48 - Home-Manager — not because it checks the platform, but because it produces 49 - configs for multiple classes and Den picks the relevant class module according to the configuration domain: 62 + Aspects form a directed acyclic graph through `includes` and form a tree of related aspects using `provides`. 50 63 51 64 ```nix 52 - { host, user, ... }: { 53 - nixos.users.users.${user.userName}.isNormalUser = true; 54 - darwin.users.users.${user.userName}.home = "/Users/${user.userName}"; 55 - homeManager.home.username = user.userName; 56 - } 65 + den.aspects.workstation = { 66 + includes = [ 67 + den.aspects.dev-tools 68 + den.aspects.gaming.provides.emulation 69 + den.provides.primary-user 70 + ]; 71 + nixos.services.xserver.enable = true; 72 + }; 57 73 ``` 58 74 59 - ### Composability 60 75 61 - Aspects compose through `includes` — a directed dependency graph. 62 - The [`parametric`](/explanation/parametric/) functor ensures context flows through the entire graph: 76 + ## Separation of Concerns 63 77 64 - ```mermaid 65 - graph TD 66 - D["den.my-laptop"] --> WS["workspace"] 67 - D --> DU["common-users-env"] 68 - WS --> VPN["vpn"] 69 - WS --> DEV["dev-tools"] 70 - U["den.aspects.vic"] --> PU["primary-user"] 71 - U --> SH["user-shell 'fish'"] 72 - ``` 73 - 74 - ### Separation of Concerns 75 - 76 - Each Dendritic file is focused on a single responsability. Configuring it across all different domains. Configurations are parametrized by data outside of their module config. 78 + Den separates **what exists** (schema) from **what it does** (aspects): 77 79 78 - ## The Result 80 + | Layer | Purpose | Example | 81 + |-------|---------|---------| 82 + | Schema | Declare entities | `den.hosts.x86_64-linux.laptop.users.alice = {}` | 83 + | Aspects | Configure behavior | `den.aspects.laptop.nixos.networking.hostName = "laptop"` | 84 + | Context | Transform data flow | `den.ctx.host` produces `{host}`, then `{host, user}` per user | 85 + | Batteries | Reusable patterns | `den.provides.primary-user`, `den.provides.user-shell` | 79 86 80 - A system where: 81 - - **Declaring** entities is separate from **configuring** them 82 - - **Configurations** are separate from **when they apply** 83 - - [**Aspects**](/explanation/aspects/) compose without knowing each other’s details 84 - - [**Context**](/explanation/context-system/) flows declaratively through a [typed pipeline](/explanation/context-pipeline/) 87 + This separation means you can reorganize files, rename aspects, or add platforms 88 + without restructuring your configuration logic.
+74 -70
docs/src/content/docs/explanation/library-vs-framework.mdx
··· 1 1 --- 2 2 title: Library vs Framework 3 - description: Understanding Den's dual nature as both a library and a framework. 3 + description: Using Den's core lib for any Nix domain beyond OS configuration. 4 4 --- 5 5 6 - 7 - ## Den the Library 8 - 9 - At its core, Den is a **library** for activating configuration aspects via context transformations. 10 - 11 - The library provides: 12 - 13 - - [**`den.ctx`**](/reference/ctx/) — Declarative context types with transformations 14 - - [**`den.lib.parametric`**](/reference/lib/#denlibparametric) — Functors for context-aware dispatch 15 - - [**`den.lib.take`**](/reference/lib/#denlibtake) — Argument matching (`atLeast`, `exactly`) 16 - - [**`den.lib.aspects`**](/reference/lib/#denlibaspects) — Aspect composition (via [flake-aspects](https://github.com/vic/flake-aspects)) 17 - - [**`den.lib.canTake`**](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) — Function signature introspection 18 - - [**`den.lib.__findFile`**](/guides/angle-brackets/) — Angle-bracket resolution 19 - 20 - These tools work with **any Nix domain**. You can define context types 21 - for terraform modules, NixVim configs, container definitions, or anything 22 - else configurable through Nix. 6 + import { Aside } from '@astrojs/starlight/components'; 23 7 24 - ## Den the Framework 8 + ## Den as a Library 25 9 26 - On top of the library, Den provides a **framework** for the common case 27 - of managing NixOS/Darwin/Home-Manager configurations: 28 - 29 - - [**`den.hosts`**](/reference/schema/#denhosts) — Host declaration with freeform schema 30 - - [**`den.homes`**](/reference/schema/#denhomes) — Standalone Home-Manager declaration 31 - - [**`den.base`**](/reference/schema/#denbase) — Base modules for entities 32 - - [**`den.default`**](/explanation/context-pipeline/#dendefault-is-an-alias) — Global contributions to hosts / users / homes. 33 - - [**`den.provides`**](/reference/batteries/) — Batteries (define-user, unfree, etc.) 34 - - **Built-in `den.ctx` types** — [host, user, hm-host, hm-user, home](/reference/ctx/#built-in-context-types) 10 + Den's core (`nix/lib.nix`) is domain-agnostic. It provides: 35 11 36 - The framework is the **first implementation** using the library, not 37 - the only possible one. 12 + | Function | Purpose | 13 + |---|---| 14 + | `parametric` | Wrap an aspect with context-aware dispatch | 15 + | `parametric.atLeast` | Match functions with at least the given params | 16 + | `parametric.exactly` | Match functions with exactly the given params | 17 + | `parametric.fixedTo` | Apply an aspect with a fixed context | 18 + | `parametric.expands` | Extend context before dispatch | 19 + | `canTake` | Check if a function accepts given arguments | 20 + | `take.atLeast` / `take.exactly` | Conditionally apply functions | 21 + | `statics` | Extract only static includes from an aspect | 22 + | `owned` | Extract only owned configs (no includes, no functor) | 23 + | `isFn` | Check if value is a function or has `__functor` | 24 + | `__findFile` | Angle bracket resolution for deep aspect paths | 25 + | `aspects` | The flake-aspects API (resolve, merge, types) | 38 26 39 - ## Using Den as a Library 27 + These primitives compose into context transformation pipelines for any domain. 40 28 41 - Context types are independent of NixOS or OS configurations. Den can 42 - configure network topologies, declarative cloud infrastructure, container 43 - orchestration — anything describable as data transformations over 44 - Nix-configurable systems. 29 + ## Using Den for Non-OS Domains 45 30 46 - Here is a deployment pipeline example: 31 + Nothing about `den.lib` assumes NixOS, Darwin, or Home Manager. You can build 32 + context pipelines for any Nix module system: 47 33 48 34 ```nix 49 - den.ctx.deploy._.deploy = { target }: 50 - den.aspects.${target.name}; 51 - 52 - den.ctx.deploy.into.service = { target }: 53 - map (s: { service = s; target = target; }) 54 - target.services; 35 + { my-aspects, den, ... }: { 55 36 56 - den.ctx.service._.service = { service, target }: 57 - { terraform.resource.${service.name} = { ... }; }; 58 - ``` 37 + # create a private isolated aspect namespace independent of den.aspects 38 + imports = [ (den.namespace "my-aspects" false) ]; 59 39 60 - This creates a deployment pipeline where: 61 - 1. Each deploy target has an aspect 62 - 2. Targets enumerate their services 63 - 3. Each service produces Terraform configurations 40 + # Define aspects for a custom domain 41 + my-aspects = { 42 + web-server = den.lib.parametric { 43 + terranix.resource.aws_instance.web = { ami = "..."; }; 44 + includes = [ 45 + # configures using the terranix Nix class 46 + ({ env, ... }: { terranix.resource.aws_instance.web.tags.Env = env; }) 47 + ]; 48 + }; 49 + }; 64 50 65 - The same `ctxApply` mechanics — owned configs, self-named provider lookup, 66 - `into` transformations — drive this pipeline without any OS-specific code. 51 + # Resolve for your custom class 52 + aspect = my-aspects.web-server { env = "production"; }; 53 + module = aspect.resolve { class = "terranix"; }; 54 + } 55 + ``` 67 56 68 - You can even design and test custom context flows independently of 69 - building any configuration, using Den's test infrastructure. 57 + ## Den as a Framework 70 58 71 - Obtaining the final Nix configuration of `terraform` class is like: 59 + On top of the library, Den provides `modules/` which implement: 72 60 73 - ```nix 74 - aspect = den.ctx.deploy { target.name = "web"; }; 61 + - **Schema types** (`den.hosts`, `den.homes`) for declaring NixOS/Darwin/HM entities 62 + - **Context pipeline** (`den.ctx.host`, `den.ctx.user`, `den.ctx.home`) for OS configurations 63 + - **Batteries** (`den.provides.*`) for common OS configuration patterns 64 + - **Output generation** (`modules/config.nix`) instantiating configurations into flake outputs 75 65 76 - module = aspect.resolve { class = "terranix"; }; 66 + The framework is entirely optional. You can use `den.lib` directly without 67 + any of the `den.hosts`/`den.aspects` machinery. 77 68 78 - terranix.lib.terranixConfiguration { 79 - modules = [ module ]; 80 - }; 69 + ```mermaid 70 + flowchart TD 71 + subgraph "Den Library (domain-agnostic)" 72 + parametric["parametric dispatch"] 73 + canTake["canTake / take"] 74 + aspects["flake-aspects API"] 75 + end 76 + subgraph "Den Framework (OS-specific)" 77 + schema["den.hosts / den.homes"] 78 + ctx["den.ctx pipeline"] 79 + batteries["den.provides"] 80 + output["flake output generation"] 81 + end 82 + parametric --> ctx 83 + canTake --> parametric 84 + aspects --> parametric 85 + schema --> ctx 86 + batteries --> ctx 87 + ctx --> output 81 88 ``` 82 89 83 - ## No Lock-in 84 - 85 - Because the framework is built on the library: 86 - 87 - - You can use the library without the framework 88 - - You can replace any framework component 89 - - You can mix Den-managed and non-Den configs 90 - - You can extend the framework with custom context types 91 - - You can adopt incrementally — one host at a time 90 + ## When to Use What 92 91 93 - Den doesn't force a project structure, it plays well with anything you choose, be it flakes, npins, flake-parts, falake, or other module systems. 92 + - **Library only**: You have a custom Nix module system (Terranix, NixVim, system-manager) 93 + and want parametric aspect dispatch without Den's host/user/home framework. 94 + - **Framework**: You configure NixOS/Darwin/Home Manager hosts and want the full 95 + pipeline with batteries, schema types, and automatic output generation. 96 + - **Both**: Use the framework for OS configs and the library for additional domains 97 + within the same flake.
+64 -166
docs/src/content/docs/explanation/parametric.mdx
··· 1 1 --- 2 2 title: Parametric Aspects 3 - description: How parametric functors enable context forwarding and adaptation. 3 + description: Context-aware function dispatch via argument introspection. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github"> 9 - [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) 8 + <Aside title="Source" icon="github"> 9 + [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) -- 10 + [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) 10 11 </Aside> 11 12 12 - ## What Is a Parametric Aspect? 13 + ## How Parametric Dispatch Works 13 14 14 - A **parametric** aspect delegates its [implicit arguments](https://sngeth.com/functional%20programming/2024/09/25/point-free-style/) to functions defined in its `.includes`. 15 - 16 - 17 - The result of a `parametric` functions 18 - is an aspect that looks like this: 15 + Den inspects function arguments at evaluation time using `builtins.functionArgs`. 16 + A function's **required parameters** determine which contexts it matches: 19 17 20 18 ```nix 21 - foo = { 22 - # context propagation into includes 23 - __functor = self: ctx: 24 - map (f: f ctx) self.includes; 25 - 26 - # owned configs 27 - nixos.foo = 1; 28 - 29 - includes = [ 30 - # functions receiving context 31 - (ctx: { nixos.bar = 2; }) 32 - ]; 33 - } 34 - ``` 19 + # Matches: {host}, {host, user}, {host, user, foo} 20 + ({ host, ... }: { nixos.x = host.hostName; }) 35 21 36 - When applied `foo { x = 1; }` the context 37 - is propagated to the aspect includes. 22 + # Matches: {host, user} only 23 + (den.lib.take.exactly ({ host, user }: { nixos.x = user.userName; })) 38 24 39 - Den provides several parametric functors in 40 - `den.lib.parametric`. Each of them provides a 41 - different `__functor` beaviour. 42 - 43 - <Aside > 44 - Aspects created by Den from your host/user/home definitions are already `parametric`. 45 - They will forward context to their includes functions. 46 - 47 - However for new aspects you define manually, they are not parametric unless you say so. 48 - If you get the following error: 49 - 50 - ```error 51 - error: function 'anonymous lambda' called without required argument 'user' 25 + # Matches: {home} **or** {home, foo} 26 + ({ home }: { homeManager.x = home.userName; }) 52 27 ``` 53 28 54 - It likely means you are trying to forward context from an aspect that is not `parametric`. 55 - See also [#182](https://github.com/vic/den/discussions/182) 56 - </Aside> 29 + ## The `canTake` Function 57 30 58 - 59 - ## den.lib.parametric 60 - 61 - The most common functor. When applied it 62 - includes the aspect owned config as well as 63 - the result of applying the context to 64 - included functions that support `atLeast` the 65 - same context. 66 - 31 + `den.lib.canTake` (`nix/fn-can-take.nix`) checks if a function's required arguments 32 + are satisfiable with given parameters: 67 33 68 34 ```nix 69 - # NOTE: context is known until application 70 - foo = den.lib.parametric { 71 - # always included 72 - nixos.networking.hostName = "owned"; 35 + canTake { host = ...; } ({ host, ... }: ...) # => true (atLeast) 36 + canTake { host = ...; } ({ host, user }: ...) # => false (user missing) 73 37 74 - includes = [ 75 - # context received here 76 - ({ host, ... }: { nixos.time.timeZone = "UTC"; }) 77 - ]; 78 - }; 38 + canTake.exactly { host = ...; } ({ host }: ...) # => true 39 + canTake.exactly { host = ...; foo = ...; } ({ host, ... }: ...) # => false (extras) 79 40 ``` 80 41 81 - When applied with `foo { host = ...; user = ...; }`: 42 + - `canTake.atLeast` -- all required params are present (default). 43 + - `canTake.exactly` -- required params match provided params exactly. 82 44 83 - 1. **Owned** configs (`nixos.networking.hostName`) are included 84 - 2. **Static** includes are included 85 - 3. **Functions** matching `atLeast` the context args are called 45 + ## The `take` Utilities 86 46 87 - ## parametric.atLeast 88 - 89 - Only dispatches to includes — does **not** contribute owned configs: 47 + `den.lib.take` wraps `canTake` into aspect-ready helpers: 90 48 91 49 ```nix 92 - foo = parametric.atLeast { 93 - nixos.ignored = 22; 94 - includes = [ 95 - ({ x, ...}: { nixos.x = x; }) 96 - ({ x, y }: { nixos.y = y; }) 97 - ({ z }: { nixos.z = z; }) 98 - ]; 99 - }; 100 - ``` 101 - 102 - Applied with `foo { x = 1; y = 2; }`: 103 - - `{ x, ... }` matches (context has at least x) 104 - - `{ x, y }` matches (context has at least x y) 105 - - `{ z }` skipped (context has no z) 106 - 107 - ## parametric.exactly 108 - 109 - Only dispatches to includes — does **not** contribute owned configs: 110 - 111 - only calls functions with **exactly** matching args: 50 + # Apply fn only if ctx satisfies atLeast 51 + den.lib.take.atLeast fn ctx 112 52 113 - ```nix 114 - foo = parametric.exactly { 115 - nixos.ignored = 22; 116 - includes = [ 117 - ({ x, ...}: { nixos.x = x; }) 118 - ({ x, y }: { nixos.y = y; }) 119 - ({ z }: { nixos.z = z; }) 120 - ]; 121 - }; 53 + # Apply fn only if ctx satisfies exactly 54 + den.lib.take.exactly fn ctx 122 55 ``` 123 56 124 - Applied with `{ x = 1; y = 2; }`: 125 - - `{ x, ... }` → skipped context is larget 126 - - `{ x, y }` → called (exact match) 127 - - `{ z }` → skipped no match 57 + These are used inside `includes` to control when a function activates. 128 58 59 + ## The `parametric` Constructor 129 60 130 - ## parametric.fixedTo 131 - 132 - This is an `atLeast` functor that also 133 - contributes its **owned** configs and 134 - ignores the context it is called with, 135 - replacing it with a fixed one. 61 + `den.lib.parametric` changes an aspect `__functor` which processes 62 + its `includes` list through parametric dispatch: 136 63 137 64 ```nix 138 - foo = parametric.fixedTo { planet = "Earth"; } { 139 - nixos.foo = "contributed"; 140 - # functions have atLeast semantics 65 + den.lib.parametric { 66 + nixos.foo = 1; # owned config, always included 141 67 includes = [ 142 - ({ planet, ... }: { nixos.setting = planet; }) 68 + { nixos.bar = 2; } # static, always included 69 + ({ host }: { nixos.x = host.name; }) # parametric, host contexts only 70 + ({ user }: { homeManager.y = 1; }) # parametric, user contexts only 143 71 ]; 144 - }; 72 + } 145 73 ``` 146 74 147 - No matter what context `foo` receives, its includes always get 148 - `{ planet = "Earth"; }`. 75 + When the aspect's `__functor` is called with a context, it filters `includes` 76 + based on argument compatibility and returns only matching entries. 149 77 150 - ## parametric.expands 78 + <Aside type="caution" title="Anonymous functions are an anti-pattern"> 79 + It is **not** recommended to have inlined functions on your `.includes` lists. 80 + This guide uses inlined functions only for examples, not as best-practice. 151 81 152 - Like `fixedTo` but 153 - adds attributes to the received context: 82 + Instead, use named aspects, this will improves readability and the error traces 83 + generated by Nix since those functions have a proper name and location. 154 84 155 85 ```nix 156 - foo = parametric.expands { planet = "Earth"; } { 157 - includes = [ 158 - ({ host, planet, ... }: { 159 - nixos.setting = "${host.name}/${planet}"; 160 - }) 161 - ]; 162 - }; 86 + den.aspects.my-laptop.includes = [ foo ]; 87 + den.aspects.foo = { host }: { ... }; 163 88 ``` 89 + </Aside> 164 90 165 - Applied with `{ host = ...; }`, the includes receive 166 - `{ host = ...; planet = "Earth"; }`. 91 + ## Parametric Variants 92 + 93 + | Constructor | Behavior | 94 + |---|---| 95 + | `parametric` | Default. <br/> Includes owned classes + static includes + function includes with context matching `atLeast`. | 96 + | `parametric.atLeast` | **Does NOT** include owned classes nor static includes. Only function matching atLeast the context. | 97 + | `parametric.exactly` | Like atLeast but using canTake.exactly for match. | 98 + | `parametric.fixedTo attrs` | Like `parametric` default but ignores any context and always uses the given attrs . | 99 + | `parametric.expands attrs` | Like `parametric`, but extends the received context with `attrs` before dispatch. | 167 100 168 - ## parametric.withOwn 101 + ## Example: Context-aware battery 169 102 170 - Combinator that adds owned config and static includes on top of any 171 - dispatch functor: 103 + This is a pattern used by many of our built-in batteries, be sure to see their code as example. 172 104 173 105 ```nix 174 - parametric.withOwn parametric.atLeast { 175 - nixos.foo = "owned"; # included always 106 + den.lib.parametric.exactly { 176 107 includes = [ 177 - { nixos.bar = "static"; } # included always 178 - ({ host, ... }: { ... }) # dispatched via atLeast 108 + ({ user, host }: { ... }) 109 + ({ home }: { ... }) 179 110 ]; 180 - } 181 - ``` 182 - 183 - The `parametric` function itself is an alias for `(parametric.withOwn parametric.atLeast)`. 184 - 185 - 186 - ## Matching Rules Summary 187 - 188 - | Functor | Owned configs | Statics includes | Functions includes semantics | 189 - |---------|:-----:|:-------:|:---------:| 190 - | `parametric` | ✓ | ✓ | atLeast | 191 - | `parametric.atLeast` | ✗ | ✗ | atLeast | 192 - | `parametric.exactly` | ✗ | ✗ | exactly | 193 - | `parametric.fixedTo ctx` | ✓ | ✓ | fixed ctx | 194 - | `parametric.expands ctx` | ✓ | ✓ | ctx + received | 195 - | `parametric.withOwn F` | ✓ | ✓ | uses F | 196 - 197 - ## take.exactly and take.atLeast 198 - 199 - For individual functions (not whole aspects), use [`den.lib.take`](/reference/lib/#denlibtake): 200 - 201 - 202 - ```nix 203 - foo = parametric.atLeast { 204 - includes = [ 205 - (take.exactly ({ x, y }: ... )); 206 - (take.atLeast ({ x, y }: ... )); 207 - ]; 208 111 }; 209 112 ``` 210 113 211 - Applied with `foo { x = 1; y = 2; z = 3; }`: 212 - - `exactly { x, y }` is skipped 213 - - `atLeast { x, y }` matches 214 - 215 - This mechanism prevents functions with 216 - lax context from matching everything, 217 - which would **produce duplicate config values**. 114 + The first function runs for every `{host, user}` context. The second only 115 + runs for `{home}` (standalone home-manager) contexts. No `mkIf` needed.
+43 -50
docs/src/content/docs/guides/angle-brackets.mdx
··· 1 1 --- 2 2 title: Angle Brackets Syntax 3 - description: Opt-in shorthand for resolving deep aspect paths. 3 + description: <aspect/path> shorthand via __findFile. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - 9 - <Aside title="Use the Source, Luke" icon="github">[`nix/den-brackets.nix`](https://github.com/vic/den/blob/main/nix/den-brackets.nix)</Aside> 8 + <Aside title="Source" icon="github"> 9 + [`nix/den-brackets.nix`](https://github.com/vic/den/blob/main/nix/den-brackets.nix) 10 + </Aside> 10 11 11 - <Aside type="caution">Angle brackets is an experimental, opt-in feature.</Aside> 12 + ## What it Does 12 13 13 - ## What It Does 14 + Den provides a `__findFile` implementation that resolves `<aspect/path>` 15 + expressions into deep aspect lookups. This is syntactic sugar for 16 + accessing nested aspects and provides. 14 17 15 - Den's `__findFile` resolves angle-bracket expressions to aspect paths: 18 + ## Enabling 16 19 17 - | Expression | Resolves to | 18 - |------------|-------------| 19 - | `<den.lib>` | `den.lib` | 20 - | `<den.default>` | `den.default` | 21 - | `<igloo>` | `den.aspects.igloo` | 22 - | `<foo/bar>` | `den.aspects.foo.provides.bar` | 23 - | `<foo/bar/baz>` | `den.aspects.foo.provides.bar.provides.baz` | 24 - | `<ns/tools>` | `den.ful.ns.tools` | 25 - 26 - ## Enable Globally 27 - 28 - Create a module that sets it for all modules: 20 + Set `__findFile` via module args: 29 21 30 22 ```nix 31 23 { den, ... }: { ··· 33 25 } 34 26 ``` 35 27 36 - Then use it anywhere: 28 + ## Resolution Rules 37 29 38 - ```nix 39 - { __findFile, ... }: { 40 - den.default.includes = [ <den/define-user> ]; 41 - den.aspects.igloo.includes = [ <foo/bar/baz> ]; 42 - } 43 - ``` 30 + The `<name>` expression resolves through these paths in order: 31 + 32 + 1. **`<den.x.y>`** -- resolves to `config.den.x.y` 33 + 2. **`<aspect>`** -- resolves to `config.den.aspects.aspect` (if `aspect` exists in `den.aspects`) 34 + 3. **`<aspect/sub>`** -- resolves to `config.den.aspects.aspect.provides.sub` 35 + 4. **`<namespace>`** -- resolves to `config.den.ful.namespace` (if it is a denful) 44 36 45 - ## Enable via Let-Binding 37 + The `/` separator is translated to `.provides.` in the lookup path. 46 38 47 - For a single lexical scope: 39 + ## Examples 48 40 49 41 ```nix 50 - { den, ... }: 51 - let 52 - inherit (den.lib) __findFile; 53 - in { 54 - den.aspects.igloo.includes = [ <den/define-user> ]; 55 - } 56 - ``` 42 + # Without angle brackets 43 + den.aspects.laptop.includes = [ 44 + den.aspects.tools.provides.editors 45 + den.aspects.alice.provides.work-vpn 46 + den.provides.primary-user 47 + ]; 57 48 58 - ## Namespace Access 59 - 60 - With a namespace `ns` enabled: 61 - 62 - ```nix 63 - { __findFile, ... }: { 64 - ns.moo.silly = true; # write access 65 - expr = <ns/moo>; # read resolves to den.ful.ns.moo 66 - } 49 + # With angle brackets 50 + den.aspects.laptop.includes = [ 51 + <tools/editors> 52 + <alice/work-vpn> 53 + <den.provides.primary-user> 54 + ]; 67 55 ``` 68 56 69 - ## Deep Nested Provides 57 + ## When to Use 70 58 71 - Slashes translate to `.provides.` in the aspect tree: 59 + Angle brackets are optional syntactic sugar. They are useful when: 60 + - You have deeply nested provides and want shorter references. 61 + - You are working with namespaces and want concise cross-references. 62 + 63 + They are functionally identical to direct attribute access. The choice 64 + is a matter of style. 72 65 73 - ```nix 74 - den.aspects.foo.provides.bar.provides.baz.nixos.programs.fish.enable = true; 75 - den.aspects.igloo.includes = [ <foo/bar/baz> ]; 76 - # igloo gets fish enabled 77 - ``` 66 + <Aside type="caution"> 67 + Angle brackets use Nix's `__findFile` mechanism, which is positional 68 + (depends on being in scope). They only work in modules where 69 + `__findFile` has been set as a module argument. 70 + </Aside>
+97 -57
docs/src/content/docs/guides/batteries.mdx
··· 1 1 --- 2 2 title: Use Batteries 3 - description: Opt-in, replaceable aspects for common tasks. 3 + description: Built-in den.provides.* aspects for common tasks. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github"> 9 - [`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides) · Reference: [Batteries](/reference/batteries/) 8 + <Aside title="Source" icon="github"> 9 + [`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides) 10 10 </Aside> 11 11 12 - ## What Are Batteries? 12 + ## What are Batteries 13 13 14 - Batteries are pre-built aspects shipped with Den. They are **opt-in** — you 15 - explicitly include them where needed. They are **replaceable** — you can 16 - write your own alternative. 14 + Batteries are reusable aspects shipped with Den under `den.provides.*` 15 + (aliased as `den._.*`). They handle common cross-platform configuration 16 + patterns so you do not have to rewrite them. 17 17 18 - Access batteries via `den._.` (shorthand for `den.provides.`). 18 + ## Available Batteries 19 19 20 - ## define-user 20 + ### `den.provides.define-user` 21 21 22 - Defines a user at both OS and Home-Manager levels: 22 + Creates OS and home-level user account definitions: 23 23 24 24 ```nix 25 - den.default.includes = [ den._.define-user ]; 25 + den.default.includes = [ den.provides.define-user ]; 26 26 ``` 27 27 28 - Sets `users.users.<name>`, `home.username`, and `home.homeDirectory` 29 - automatically for NixOS, Darwin, and standalone Home-Manager. 28 + Sets `users.users.<name>` on NixOS/Darwin and `home.username`/`home.homeDirectory` 29 + for Home Manager. Works in both host-user and standalone home contexts. 30 30 31 - ## primary-user 31 + ### `den.provides.primary-user` 32 32 33 - Makes a user an administrator: 33 + Marks a user as the primary user of the system: 34 34 35 35 ```nix 36 - den.aspects.vic.includes = [ den._.primary-user ]; 36 + den.aspects.alice.includes = [ den.provides.primary-user ]; 37 37 ``` 38 38 39 - On NixOS: adds `wheel` and `networkmanager` groups. 40 - On Darwin: sets `system.primaryUser`. 41 - On WSL: sets `wsl.defaultUser` if host has a `wsl.enable = true`. 39 + - **NixOS**: adds `wheel` and `networkmanager` groups, sets `isNormalUser`. 40 + - **Darwin**: sets `system.primaryUser`. 41 + - **WSL**: sets `defaultUser` (if WSL is enabled). 42 42 43 - ## user-shell 43 + ### `den.provides.user-shell` 44 44 45 - Sets a user's default shell at OS and HM levels: 45 + Sets the default login shell at both OS and Home Manager levels: 46 46 47 47 ```nix 48 - den.aspects.vic.includes = [ (den._.user-shell "fish") ]; 48 + den.aspects.alice.includes = [ (den.provides.user-shell "fish") ]; 49 49 ``` 50 50 51 - Enables the shell program on both NixOS/Darwin and Home-Manager, 52 - and sets it as the user's default shell. 51 + Enables `programs.<shell>.enable` on the OS and in Home Manager, 52 + and sets `users.users.<name>.shell`. 53 + 54 + ### `den.provides.forward` 55 + 56 + Creates custom Nix classes by forwarding module contents into target 57 + submodule paths. See [Custom Nix Classes](/guides/custom-classes/) for details. 53 58 54 - ## unfree 59 + ### `den.provides.import-tree` 55 60 56 - Enables unfree packages by name: 61 + Recursively imports non-dendritic `.nix` files, auto-detecting class from 62 + directory names (`_nixos/`, `_darwin/`, `_homeManager/`): 57 63 58 64 ```nix 59 - den.aspects.my-laptop.includes = [ (den._.unfree [ "discord" "slack" ]) ]; 65 + # Import per host 66 + den.ctx.host.includes = [ (den.provides.import-tree._.host ./hosts) ]; 67 + 68 + # Import per user 69 + den.ctx.user.includes = [ (den.provides.import-tree._.user ./users) ]; 70 + 71 + # Import for a specific aspect 72 + den.aspects.laptop.includes = [ (den.provides.import-tree ./disko) ]; 60 73 ``` 61 74 62 - Works for any class — NixOS, Darwin, Home-Manager. 75 + Requires `inputs.import-tree`. 63 76 64 - ## tty-autologin 77 + ### `den.provides.unfree` 65 78 66 - Enables automatic tty login for a given username: 79 + Enables specific unfree packages by name: 67 80 68 81 ```nix 69 - den.aspects.my-laptop.includes = [ (den._.tty-autologin "root") ]; 82 + den.aspects.laptop.includes = [ 83 + (den.provides.unfree [ "nvidia-x11" "steam" ]) 84 + ]; 70 85 ``` 71 86 72 - ## import-tree 87 + Works for any class (`nixos`, `darwin`, `homeManager`). The unfree predicate 88 + builder is automatically included via `den.default`. 73 89 74 - Recursively imports non-dendritic Nix files by class: 90 + ### `den.provides.tty-autologin` 91 + 92 + Enables automatic TTY1 login on NixOS: 75 93 76 94 ```nix 77 - den.ctx.host.includes = [ (den._.import-tree._.host ./hosts) ]; 78 - den.ctx.user.includes = [ (den._.import-tree._.user ./users) ]; 95 + den.aspects.laptop.includes = [ (den.provides.tty-autologin "alice") ]; 79 96 ``` 80 97 81 - Given a directory structure like `./hosts/my-laptop/_nixos/`, it auto-imports 82 - files under the matching class directory. Useful for migration. 98 + ### `den.provides.inputs'` 99 + 100 + Provides flake-parts `inputs'` (system-specialized inputs) as a module argument: 101 + 102 + ```nix 103 + den.default.includes = [ den.provides.inputs' ]; 104 + ``` 105 + 106 + Requires flake-parts. Works in host, user, and home contexts. 83 107 84 - ## inputs' and self' (flake-parts) 108 + ### `den.provides.self'` 85 109 86 - Provide per-system `inputs'` and `self'` to NixOS/HM modules: 110 + Provides flake-parts `self'` (system-specialized self) as a module argument: 87 111 88 112 ```nix 89 - den.default.includes = [ den._.inputs' den._.self' ]; 113 + den.default.includes = [ den.provides.self' ]; 114 + ``` 115 + 116 + Requires flake-parts. Works in host, user, and home contexts. 117 + 118 + ## Usage Patterns 119 + 120 + ### Global Batteries 121 + 122 + Apply to all entities via `den.default`: 90 123 91 - den.aspects.igloo.nixos = { inputs', ... }: { 92 - environment.systemPackages = [ inputs'.nixpkgs.legacyPackages.hello ]; 93 - }; 124 + ```nix 125 + den.default.includes = [ 126 + den.provides.define-user 127 + den.provides.inputs' 128 + ]; 94 129 ``` 95 130 96 - ## forward 131 + ### Per-Aspect Batteries 97 132 98 - Create custom Nix classes that forward configs into target submodules: 133 + Apply to specific aspects: 99 134 100 135 ```nix 101 - den.aspects.igloo.includes = [ 102 - ({ class, aspect-chain }: 103 - den._.forward { 104 - each = lib.singleton class; 105 - fromClass = _: "custom"; 106 - intoClass = _: "nixos"; 107 - intoPath = _: [ ]; 108 - fromAspect = _: lib.head aspect-chain; 109 - guard = { options, ... }: options ? custom; # optional 110 - }) 136 + den.aspects.alice.includes = [ 137 + den.provides.primary-user 138 + (den.provides.user-shell "zsh") 139 + (den.provides.unfree [ "vscode" ]) 111 140 ]; 112 141 ``` 113 142 114 - This is how Home-Manager, nix-maid, and hjem integrations are implemented internally. 115 - Use `guard` to forward only when target options exist. 143 + ### Battery Composition 144 + 145 + Batteries compose with regular aspects: 146 + 147 + ```nix 148 + den.aspects.my-admin = den.lib.parametric { 149 + includes = [ 150 + den.provides.primary-user 151 + (den.provides.user-shell "fish") 152 + { nixos.security.sudo.wheelNeedsPassword = false; } 153 + ]; 154 + }; 155 + ```
+92 -84
docs/src/content/docs/guides/configure-aspects.mdx
··· 1 1 --- 2 2 title: Configure Aspects 3 - description: Recipes for configuring aspects — owned configs, includes, providers, and bidirectional dependencies. 3 + description: Owned configs, includes, provides, and den.default. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github"> 8 + <Aside title="Source" icon="github"> 9 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) 10 10 </Aside> 11 11 12 - ## Static Host Config 12 + ## Aspect Basics 13 13 14 - Set NixOS/Darwin/HM options directly on an aspect: 14 + An aspect is an attrset with per-class configs, an `includes` list, and 15 + optional `provides` sub-aspects: 15 16 16 17 ```nix 17 - den.aspects.igloo = { 18 - nixos = { pkgs, ... }: { 19 - environment.systemPackages = [ pkgs.hello ]; 18 + { den, ... }: { 19 + den.aspects.laptop = { 20 + # Owned configs per class 21 + nixos = { pkgs, ... }: { 22 + environment.systemPackages = [ pkgs.git ]; 23 + }; 24 + darwin.nix-homebrew.enable = true; 25 + homeManager.programs.starship.enable = true; 26 + 27 + # Dependencies 28 + includes = [ 29 + den.aspects.dev-tools 30 + den.provides.primary-user 31 + ]; 32 + 33 + # Named sub-aspects 34 + provides.gpu = { host, ... }: 35 + lib.optionalAttrs (host ? gpu && host.gpu == "nvidia") { 36 + nixos.hardware.nvidia.enable = true; 37 + }; 20 38 }; 21 - homeManager.programs.vim.enable = true; 22 - }; 39 + } 23 40 ``` 24 41 25 - ## Include Other Aspects 42 + ## Owned Configs 43 + 44 + Attributes named after a Nix class (`nixos`, `darwin`, `homeManager`, or any 45 + custom class) are **owned configs**. They can be: 26 46 27 - Compose aspects with `includes`: 47 + - **Attrsets**: merged directly into the class configuration. 48 + - **Functions**: called with the class's module arguments (`{ config, pkgs, lib, ... }`). 28 49 29 50 ```nix 30 - den.aspects.tux = { 31 - includes = [ 32 - den.provides.primary-user 33 - (den.provides.user-shell "fish") 34 - den.aspects.base-tools 35 - ]; 51 + # Attrset form 52 + den.aspects.laptop.nixos.networking.hostName = "laptop"; 53 + 54 + # Function form 55 + den.aspects.laptop.nixos = { pkgs, ... }: { 56 + environment.systemPackages = [ pkgs.git ]; 36 57 }; 37 58 ``` 38 59 39 - ## Context-Aware Includes 60 + ## Includes 40 61 41 - Functions in `includes` receive context and only activate when they match: 62 + `includes` is a list of aspects, attrsets, or parametric functions: 42 63 43 64 ```nix 44 - den.aspects.igloo.includes = [ 45 - den.aspects.video 46 - ]; 47 - 48 - den.aspects.video = den.lib.take.exactly ({ host, user }: { 49 - nixos.users.users.${user.userName}.extraGroups = [ "video" ]; 50 - }); 65 + den.aspects.workstation.includes = [ 66 + # Reference another aspect (its full DAG is included) 67 + den.aspects.dev-tools 51 68 52 - ``` 69 + # Static attrset (always applied) 70 + { nixos.programs.vim.enable = true; } 53 71 54 - This runs **once per user** on the host. A function taking `{ host }` only runs at the host level. 55 - 56 - ## Host Configures Its Users 57 - 58 - A host aspect can contribute to user-level settings: 72 + # Parametric function (applied when context shape matches) 73 + ({ host, ... }: { nixos.networking.hostName = host.hostName; }) 59 74 60 - ```nix 61 - den.aspects.igloo = { 62 - nixos.networking.hostName = "igloo"; 63 - homeManager.programs.direnv.enable = true; 64 - }; 75 + # Battery 76 + (den.provides.user-shell "fish") 77 + ]; 65 78 ``` 66 79 67 - The `homeManager` config from a host aspect becomes the default for **all users** on that host. 68 - 69 - ## User Configures Its Host 80 + ## Provides 70 81 71 - A user aspect can contribute to the host's NixOS/Darwin config: 82 + `provides` creates named sub-aspects accessible via `den.aspects.<name>._.<sub>` 83 + or `den.aspects.<name>.provides.<sub>`: 72 84 73 85 ```nix 74 - den.aspects.tux = { 75 - nixos.users.users.tux.description = "cute penguin"; 76 - homeManager = { pkgs, ... }: { 77 - home.packages = [ pkgs.htop ]; 78 - }; 86 + den.aspects.tools.provides.editors = { 87 + homeManager.programs.helix.enable = true; 88 + homeManager.programs.vim.enable = true; 79 89 }; 90 + 91 + # Used elsewhere: 92 + den.aspects.alice.includes = [ den.aspects.tools._.editors ]; 80 93 ``` 81 94 82 - The `nixos` config from a user aspect is applied to **every host** the user appears on. 83 - 84 - 85 - ## Conditional Config by Host/User 86 - 87 - Use aspect arguments to specialize: 95 + `provides` can also be parametric functions: 88 96 89 97 ```nix 90 - den.aspects.igloo.includes = [ den.aspects.tuxGit ]; 91 - 92 - den.aspects.tuxGit = den.lib.take.exactly ({ host, user }: 93 - if user.userName == "tux" 94 - then { homeManager.programs.git.enable = true; } 95 - else { } 96 - ); 98 + den.aspects.alice.provides.work-vpn = { host, user, ... }: 99 + lib.optionalAttrs host.hasVpn { 100 + nixos.services.openvpn.servers.work.config = "..."; 101 + }; 97 102 ``` 98 103 99 104 ## Global Defaults 100 105 101 - Use `den.default` for settings that apply everywhere: 106 + `den.default` is a special aspect applied to every host, user, and home: 102 107 103 108 ```nix 104 - den.default = { 105 - nixos.system.stateVersion = "25.11"; 106 - homeManager.home.stateVersion = "25.11"; 107 - includes = [ 108 - den.provides.define-user 109 - (den.lib.take.exactly ({ host }: { 110 - nixos.networking.hostName = host.hostName; 111 - })) 112 - ]; 113 - }; 109 + { 110 + den.default = { 111 + nixos.system.stateVersion = "25.11"; 112 + homeManager.home.stateVersion = "25.11"; 113 + includes = [ 114 + den.provides.define-user 115 + den.provides.inputs' 116 + ]; 117 + }; 118 + } 114 119 ``` 115 120 116 - Use `den.lib.take.exactly` to restrict a function to a specific context shape (e.g., only at host level, not user level). 121 + <Aside type="caution"> 122 + Owned configs from `den.default` are deduplicated across pipeline stages. 123 + Parametric functions in `den.default.includes` are evaluated at every context 124 + stage. Use `den.lib.take.exactly` if a function should only run in specific contexts. 125 + </Aside> 117 126 118 - ## Compose Named Sub-aspects 127 + ## Bidirectional Flow 119 128 120 - Organize aspects hierarchically using `provides`: 129 + Users configure their hosts. Hosts configure their users. Aspects flow 130 + in both directions: 121 131 122 132 ```nix 123 - den.aspects.gaming = { 124 - nixos.programs.steam.enable = true; 125 - provides.emulation.nixos.programs.retroarch.enable = true; 126 - provides.streaming = { 127 - nixos.services.sunshine.enable = true; 128 - homeManager.programs.moonlight.enable = true; 129 - }; 133 + # User aspect contributes to host's NixOS config 134 + den.aspects.alice = { 135 + nixos.users.users.alice.extraGroups = [ "docker" ]; 136 + homeManager = { pkgs, ... }: { home.packages = [ pkgs.htop ]; }; 130 137 }; 131 138 132 - den.aspects.my-pc.includes = [ 133 - den.aspects.gaming 134 - den.aspects.gaming._.emulation 135 - ]; 139 + # Host aspect contributes to all its users' home config 140 + den.aspects.laptop = { 141 + homeManager.programs.ssh.enable = true; 142 + nixos.services.openssh.enable = true; 143 + }; 136 144 ```
+113 -77
docs/src/content/docs/guides/custom-classes.mdx
··· 1 1 --- 2 2 title: Custom Nix Classes 3 - description: Create new Nix configuration classes with den._.forward. 3 + description: Create new classes via den.provides.forward. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`modules/aspects/provides/forward.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) · [forward CI tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/forward-from-custom-class.nix)</Aside> 8 + <Aside title="Source" icon="github"> 9 + [`modules/aspects/provides/forward.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) -- 10 + [`modules/aspects/provides/os-user.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/os-user.nix) -- 11 + [forward tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/forward-from-custom-class.nix) 12 + </Aside> 9 13 10 - ## What Are Custom Classes? 14 + ## What is a Custom Class 11 15 12 - Den natively supports `nixos`, `darwin`, and `homeManager` classes. But you 13 - can create your own classes that forward their settings into any target 14 - submodule. This is exactly how Home-Manager integration works internally. 16 + Den's built-in classes (`nixos`, `darwin`, `homeManager`) map to well-known 17 + NixOS module systems. But you can define **custom classes** that forward their 18 + contents into a target submodule path on another class. 15 19 16 - ## The forward Battery 20 + This is how Den implements: 21 + - The `user` class (forwards to `users.users.<name>` on the OS) 22 + - Home Manager integration (forwards `homeManager` to `home-manager.users.<name>`) 23 + - hjem integration (forwards `hjem` to `hjem.users.<name>`) 24 + - nix-maid integration (forwards `maid` to `users.users.<name>.maid`) 17 25 18 - `den._.forward` creates an aspect that takes configs from a source class 19 - and inserts them into a target class at a specified path. 26 + ## The `forward` Battery 20 27 21 - ### Example: Den `user` class works on both NixOS and nix-Darwin 28 + `den.provides.forward` creates a new class by forwarding its module contents 29 + into a target path on an existing class: 30 + 31 + ```nix 32 + { host }: 33 + den.provides.forward { 34 + each = lib.attrValues host.users; 35 + fromClass = user: "user"; 36 + intoClass = user: host.class; 37 + intoPath = user: [ "users" "users" user.userName ]; 38 + fromAspect = user: den.aspects.${user.aspect}; 39 + } 40 + ``` 41 + 42 + | Parameter | Description | 43 + |---|---| 44 + | `each` | List of items to forward (typically `[ user ]` or `[ true ]`) | 45 + | `fromClass` | The custom class name to read from | 46 + | `intoClass` | The target class to write into | 47 + | `intoPath` | Target attribute path in the target class | 48 + | `fromAspect` | The aspect to read the custom class from | 49 + 50 + ## Example: The Built-in `user` Class 22 51 23 - Den already provides a `user` class like this example, 24 - that forwards into the OS `user.user.<userName>` submodule. 52 + The `user` class (`modules/aspects/provides/os-user.nix`) forwards OS-level 53 + user settings without requiring Home Manager: 25 54 26 55 ```nix 27 - userClass = { host }: { class, aspect-chain }: 28 - den._.forward { 29 - each = host.users; 30 - fromClass = _user: "user"; 31 - intoClass = _user: class; # the class being resolved: nixos or darwin. 32 - intoPath = user: [ "users" "users" user.userName ]; 33 - fromAspect = user: den.aspects.${user.aspect}; 34 - adaptArgs = os-args: { osConfig = os-args.config; }; 56 + # Instead of: 57 + den.aspects.alice.nixos = { pkgs, ... } { 58 + users.users.alice = { 59 + packages = [ pkgs.hello ]; 60 + extraGroups = [ "wheel" ]; 35 61 }; 36 - 37 - # this would enable it on all hosts 38 - # den.ctx.host.includes = [ userClass ]; 62 + }; 39 63 40 - # settings at `user` class forward into host `users.users.<userName>` 41 - den.aspects.tux.user = { osConfig, pkgs, ... }: { 64 + # You write: 65 + den.aspects.alice.user = { pkgs, ... }: { 42 66 packages = [ pkgs.hello ]; 67 + extraGroups = [ "wheel" ]; 43 68 }; 44 69 ``` 45 70 46 - ### Example: A git class that forwards to home-manager. 71 + The `user` class is automatically forwarded to `users.users.<userName>` on 72 + whatever OS class the host uses (NixOS or Darwin). 73 + 74 + ## Creating Your Own Class 75 + 76 + Suppose you want a `container` class that forwards into 77 + `virtualisation.oci-containers.containers.<name>`: 78 + 79 + ```nix 80 + { den, lib, ... }: 81 + let 82 + fwd = { host, user }: 83 + den.provides.forward { 84 + each = lib.singleton user; 85 + fromClass = _: "container"; 86 + intoClass = _: host.class; 87 + intoPath = _: [ "virtualisation" "oci-containers" "containers" user.userName ]; 88 + fromAspect = _: den.aspects.${user.aspect}; 89 + }; 90 + in { 91 + den.ctx.user.includes = [ fwd ]; 92 + } 93 + ``` 94 + 95 + Now any user aspect can use the `container` class: 96 + 97 + ```nix 98 + den.aspects.alice.container = { 99 + image = "nginx:latest"; 100 + ports = [ "8080:80" ]; 101 + }; 102 + ``` 103 + 104 + ## Advanced: Guards and Adapters 105 + 106 + `forward` supports optional parameters for complex scenarios: 107 + 108 + ```nix 109 + den.provides.forward { 110 + each = lib.singleton true; 111 + fromClass = _: "wsl"; 112 + intoClass = _: host.class; 113 + intoPath = _: [ "wsl" ]; 114 + fromAspect = _: lib.head aspect-chain; 115 + # Only forward if target has wsl options 116 + guard = { options, ... }: options ? wsl; 117 + # Modify module args for the forwarded module 118 + adaptArgs = args: args // { osConfig = args.config; }; 119 + # Custom module type for the forwarded submodule 120 + adapterModule = { config._module.freeformType = lib.types.anything; }; 121 + } 122 + ``` 123 + 124 + | Parameter | Description | 125 + |---|---| 126 + | `guard` | Only forward when this predicate returns true | 127 + | `adaptArgs` | Transform module arguments before forwarding | 128 + | `adapterModule` | Custom module for the forwarded submodule type | 129 + 130 + ## User contributed examples 131 + 132 + #### Example: A git class that forwards to home-manager. 47 133 48 134 ```nix 49 135 gitClass = ··· 65 151 66 152 This will set at host: `home-manager.users.tux.programs.git.userEmail` 67 153 68 - ### Example: A `nix` class that propagates settings to NixOS and HomeManager 154 + #### Example: A `nix` class that propagates settings to NixOS and HomeManager 69 155 70 156 > Contributed by @musjj 71 157 ··· 91 177 # included at users who can fix things with nix. 92 178 den.aspects.tux.includes = [ nix-allowed ]; 93 179 ``` 94 - 95 - ## Use Cases 96 - 97 - - **User environments**: Forward a `user` class into `users.users.<name>` 98 - - **nix-maid / hjem**: Built-in integrations, see [batteries](/guides/batteries/) 99 - - **Containerization**: Forward a `container` class into systemd-nspawn configs 100 - - **VM configs**: Forward a `vm` class into microvm or QEMU settings 101 - - **Guarded classes**: Forward only when target options exist (eg, Impermanence) 102 - - **Custom tools**: Forward into any Nix-configurable system (NixVim, etc.) 103 - 104 - ## Guarded Forward 105 - 106 - Use `guard` to conditionally forward only when target options exist: 107 - 108 - ```nix 109 - # Custom `persys` forwards config into nixos.environment.persistance."/nix/persist/system" 110 - # only if environment.persistance option is present. 111 - persys = { class, aspect-chain }: den._.forward { 112 - each = lib.singleton true; 113 - fromClass = _: "persys"; 114 - intoClass = _: class; 115 - intoPath = _: [ "environment" "persistance" "/nix/persist/system" ]; 116 - fromAspect = _: lib.head aspect-chain; 117 - guard = { options, ... }@osArgs: options ? environment.persistance; 118 - }; 119 - 120 - den.hosts.my-laptop.includes = [ persys ]; 121 - 122 - # becomes nixos.environment.persistance."/nix/persist/system".hideMounts = true; 123 - den.aspects.my-laptop.persys.hideMounts = true; 124 - ``` 125 - 126 - One cool thing about these custom classes is that aspects can simply define 127 - settings at them, without having to worry if the options they depend or 128 - some feature is enabled. The guard itself is reponsible for that check 129 - in only one place, instead of having `mkIf` in a lot of places. 130 - 131 - ## Creating Your Own 132 - 133 - The `forward` function parameters: 134 - 135 - | Parameter | Description | 136 - |-----------|-------------| 137 - | `each` | List of items to iterate over | 138 - | `fromClass` | Source class name (string) | 139 - | `intoClass` | Target class name (string) | 140 - | `intoPath` | Attribute path in target (list of strings) | 141 - | `fromAspect` | Source aspect to read from | 142 - | `guard` | Optional: `{ options, ... }@args -> bool` — forward only when true | 143 - | `adaptArgs` | Optional: args->specialArgs for the intermediate submodule |
+41 -36
docs/src/content/docs/guides/debug.md
··· 3 3 description: Tools and techniques for debugging Den configurations. 4 4 --- 5 5 6 - ## builtins.trace 7 - 8 - Print values during evaluation: 9 - 10 - ```nix 11 - den.aspects.foo = { user, ... }@context: 12 - (builtins.trace context { 13 - nixos = { }; 14 - }); 15 - ``` 16 - 17 - ## builtins.break 18 - 19 - Drop into a REPL at any evaluation point: 20 - 21 - ```nix 22 - den.aspects.foo = { user, ... }@context: 23 - (builtins.break context { 24 - nixos = { }; 25 - }); 26 - ``` 27 - 28 - 29 6 ## REPL Inspection 30 7 31 8 Load your flake and explore interactively: ··· 37 14 "igloo" 38 15 ``` 39 16 40 - ## Expose den for Inspection 17 + ## Expose `den` for Inspection 41 18 42 19 Temporarily expose the `den` attrset as a flake output: 43 20 44 21 ```nix 45 22 { den, ... }: { 46 - flake.den = den; # remove when done 23 + flake.den = den; # remove after debugging 47 24 } 48 25 ``` 49 26 ··· 53 30 nix-repl> :lf . 54 31 nix-repl> den.aspects.igloo 55 32 nix-repl> den.hosts.x86_64-linux.igloo 33 + nix-repl> den.ctx 34 + ``` 35 + 36 + ## Trace Context 37 + 38 + Print context values during evaluation: 39 + 40 + ```nix 41 + den.aspects.laptop.includes = [ 42 + ({ host, ... }@ctx: builtins.trace ctx { 43 + nixos.networking.hostName = host.hostName; 44 + }) 45 + ]; 46 + ``` 47 + 48 + ## Break into REPL 49 + 50 + Drop into a REPL at any evaluation point: 51 + 52 + ```nix 53 + den.aspects.laptop.includes = [ 54 + ({ host, ... }@ctx: builtins.break ctx { 55 + nixos = { }; 56 + }) 57 + ]; 56 58 ``` 57 59 58 60 ## Manually Resolve an Aspect ··· 60 62 Test how an aspect resolves for a specific class: 61 63 62 64 ```console 63 - nix-repl> module = den.aspects.foo.resolve { class = "nixos"; aspect-chain = []; } 65 + nix-repl> module = den.aspects.laptop.resolve { class = "nixos"; aspect-chain = []; } 64 66 nix-repl> config = (lib.evalModules { modules = [ module ]; }).config 65 67 ``` 66 68 67 69 For parametric aspects, apply context first: 68 70 69 71 ```console 70 - nix-repl> aspect = den.aspects.foo { host = den.hosts.x86_64-linux.igloo; } 72 + nix-repl> aspect = den.aspects.laptop { host = den.hosts.x86_64-linux.laptop; } 71 73 nix-repl> module = aspect.resolve { class = "nixos"; aspect-chain = []; } 72 74 ``` 73 75 ··· 75 77 76 78 ```console 77 79 nix-repl> module = den.hosts.x86_64-linux.igloo.mainModule 78 - nix-repl> config = (lib.nixosSystem { modules = [ module ]; }).config 80 + nix-repl> cfg = (lib.nixosSystem { modules = [ module ]; }).config 81 + nix-repl> cfg.networking.hostName 79 82 ``` 80 83 81 84 ## Common Issues 82 85 83 - **Duplicate values in lists**: Den automatically deduplicates owned and 84 - static configs from `den.default`, but parametric functions in 85 - `den.default.includes` still run at every context stage. Use 86 - `den.lib.take.exactly` to restrict matching: 86 + **Duplicate values in lists**: Den deduplicates owned and static configs 87 + from `den.default`, but parametric functions in `den.default.includes` 88 + run at every context stage. Use `den.lib.take.exactly` to restrict: 87 89 88 90 ```nix 89 91 den.lib.take.exactly ({ host }: { nixos.x = 1; }) 90 92 ``` 91 93 92 - **Missing attribute**: The context doesn't have the expected parameter. 93 - Trace context keys to see what's available. 94 + **Missing attribute**: The context does not have the expected parameter. 95 + Trace context keys to see what is available. 96 + 97 + **Wrong class**: Check that `host.class` matches what you expect. 98 + Darwin hosts have `class = "darwin"`, not `"nixos"`. 94 99 95 - **Infinite recursion**: Aspects including each other in a cycle. 96 - Check your `includes` chains for circular dependencies. 100 + **Module not found**: Ensure the file is under `modules/` and not 101 + prefixed with `_` (excluded by import-tree).
+100 -89
docs/src/content/docs/guides/declare-hosts.mdx
··· 1 1 --- 2 2 title: Declare Hosts & Users 3 - description: Define your infrastructure entities with Den's freeform schema. 3 + description: Define machines, users, and standalone homes with den.hosts and den.homes. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github"> 8 + 9 + <Aside title="Source" icon="github"> 9 10 [`modules/_types.nix`](https://github.com/vic/den/blob/main/modules/_types.nix) · Reference: [Entity Schema](/reference/schema/) 10 11 </Aside> 11 12 12 - ## Hosts and Users 13 13 14 - Declare hosts with a single line per machine: 14 + ## Host Declaration 15 + 16 + `den.hosts` is keyed by `<system>.<name>`: 15 17 16 18 ```nix 17 - den.hosts.x86_64-linux.my-laptop.users.vic = { }; 18 - den.hosts.aarch64-darwin.macbook.users.vic = { }; 19 + { 20 + den.hosts.x86_64-linux.laptop.users.alice = { }; 21 + 22 + den.hosts.aarch64-darwin.mac = { 23 + users.alice = { }; 24 + brew.apps = [ "iterm2" ]; 25 + }; 26 + } 19 27 ``` 20 28 21 - This creates NixOS/Darwin configurations with users sharing the same home configuration, ready for: 29 + Each host entry produces a configuration in `flake.nixosConfigurations` or 30 + `flake.darwinConfigurations` depending on its `class` (auto-detected from system). 22 31 23 - ```console 24 - nixos-rebuild switch --flake .#my-laptop 25 - darwin-rebuild switch --flake .#macbook 26 - ``` 32 + ## Host Schema 27 33 28 - ## Customize Host Attributes 34 + Hosts have these options (all with sensible defaults): 29 35 30 - Expand the attribute set for full control: 36 + | Option | Default | Description | 37 + |---|---|---| 38 + | `name` | attrset key | Configuration name | 39 + | `hostName` | `name` | Network hostname | 40 + | `system` | parent key | `x86_64-linux`, `aarch64-darwin`, etc. | 41 + | `class` | `"nixos"` or `"darwin"` | OS class, auto-detected from system | 42 + | `aspect` | `name` | Primary aspect name | 43 + | `instantiate` | class-dependent | `lib.nixosSystem`, `darwinSystem`, etc. | 44 + | `intoAttr` | class-dependent | Flake output path | 45 + | `users` | `{}` | User account definitions | 46 + | `*` | from `den.base.host` | Any option defined by base module | 47 + | `*` | | Any other free-form attribute | 31 48 32 - ```nix 33 - den.hosts.x86_64-linux.my-laptop = { 34 - hostName = "yavanna"; # default: my-laptop 35 - class = "nixos"; # default: guessed from platform 36 - aspect = "workstation"; # default: my-laptop 37 - intoAttr = [ "nixosConfigurations" "yavanna" ]; # default: [ "nixosConfigurations" "my-laptop" ] 38 - users.vic = { 39 - userName = "vborja"; # default: vic 40 - aspect = "oeiuwq"; # default: vic 41 - classes = ["homeManager"]; # default: ["homeManager"] 42 - }; 43 - }; 44 - ``` 49 + ## User Declaration 45 50 46 - ## Standalone Home-Manager 47 - 48 - For Home-Manager without a host OS: 51 + Users are declared under a host: 49 52 50 53 ```nix 51 - den.homes.aarch64-darwin.vic = { }; 54 + den.hosts.x86_64-linux.laptop = { 55 + users.alice = { }; 56 + users.bob.classes = [ "homeManager" "hjem" ]; 57 + }; 52 58 ``` 53 59 54 - Build with: 60 + Each user has: 55 61 56 - ```console 57 - home-manager switch --flake .#vic 58 - ``` 62 + | Option | Default | Description | 63 + |---|---|---| 64 + | `name` | attrset key | User configuration name | 65 + | `userName` | `name` | System account name | 66 + | `aspect` | `name` | Primary aspect name | 67 + | `classes` | `[ "homeManager" ]` | Nix classes this user participates in | 68 + | `*` | from `den.base.user` | Any option defined by base module | 69 + | `*` | | Any other free-form attribute | 59 70 60 - ## Multiple Hosts, Shared Users 71 + ## Standalone Homes 61 72 62 - The same user aspect applies to every host it appears on: 73 + For systems without root access or for home-manager-only setups: 63 74 64 - ```nix "vic" 65 - den.hosts.x86_64-linux.desktop.users.vic = { }; 66 - den.hosts.x86_64-linux.server.users.vic = { }; 67 - den.hosts.aarch64-darwin.mac.users.vic = { }; 75 + ```nix 76 + { 77 + den.homes.x86_64-linux.alice = { }; 78 + den.homes.aarch64-darwin.alice = { }; 79 + } 68 80 ``` 69 81 70 - All three machines share same `den.aspects.vic` user aspect. 82 + Standalone homes produce `flake.homeConfigurations.<name>`. 71 83 72 - ## Freeform Attributes 84 + Home schema: 73 85 74 - Hosts and users accept arbitrary attributes. Use them for metadata 75 - that aspects can inspect: 86 + | Option | Default | Description | 87 + |---|---|---| 88 + | `name` | attrset key | Home configuration name | 89 + | `userName` | `name` | User account name | 90 + | `system` | parent key | Platform system | 91 + | `class` | `"homeManager"` | Home class | 92 + | `aspect` | `name` | Primary aspect name | 93 + | `pkgs` | `inputs.nixpkgs.legacyPackages.${system}` | nixpkgs instance | 94 + | `instantiate` | `inputs.home-manager.lib.homeManagerConfiguration` | Builder function | 95 + | `*` | from `den.base.host` | Any option defined by base module | 96 + | `*` | | Any other free-form attribute | 76 97 77 - ```nix 78 - den.hosts.x86_64-linux.my-laptop = { 79 - isWarm = true; # custom attribute 80 - location = "studio"; # custom attribute 81 - users.vic = { }; 82 - }; 83 - ``` 98 + ## Base Modules 84 99 85 - Then in an aspect: `{ host, ... }: if host.isWarm then ...` 100 + `den.base.{host,user,home,conf}` provides shared configuration applied to all entities of each kind. 86 101 87 - ## Base Modules 88 - 89 - Add type-checked options to all hosts, users, or homes: 102 + 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. 90 103 91 104 ```nix 92 - den.base.host = { host, lib, ... }: { 93 - options.vpn-alias = lib.mkOption { default = host.name; }; 94 - }; 105 + { 106 + # Can be used to config each host 107 + den.base.host.home-manager.enable = true; 95 108 96 - den.base.user = { user, lib, ... }: { 97 - options.main-group = lib.mkOption { default = user.name; }; 98 - }; 109 + # Can be used to add schema options with defaults 110 + den.base.user = { user, lib, ... }: { 111 + options.groupName = lib.mkOption { default = user.userName; }; 112 + }; 99 113 100 - den.base.conf = { lib, ... }: { 101 - options.org = lib.mkOption { default = "acme"; }; 102 - }; 114 + # Applied to every host and user and home. 115 + den.base.conf = { 116 + options.copyright = lib.mkOption { default = "Copy-Left"; }; 117 + }; 118 + } 103 119 ``` 104 120 105 - `den.base.conf` applies to hosts, users, **and** homes. 106 - 107 - ## Custom Instantiation 121 + ## Aspect Association 108 122 109 - Different `nixpkgs` channels per host: 123 + Each host and user has an `aspect` attribute name (defaults to the config `name`). 124 + Den looks up `den.aspects.${aspect}` to find the configuration for that entity: 110 125 111 126 ```nix 112 - den.hosts.x86_64-linux.stable-server = { 113 - instantiate = inputs.nixpkgs-stable.lib.nixosSystem; 114 - users.admin = { }; 115 - }; 127 + { 128 + den.hosts.x86_64-linux.laptop.users.alice = { }; 129 + # Den uses den.aspects.laptop for the host 130 + # Den uses den.aspects.alice for the user 131 + } 116 132 ``` 117 133 118 - Override where configurations are placed: 134 + ## Freeform Schema 135 + 136 + Host and user types use `freeformType`, so you can add arbitrary attributes: 119 137 120 138 ```nix 121 - den.hosts.x86_64-linux.wsl-box = { 122 - intoAttr = [ "wslConfigurations" "my-wsl" ]; 123 - wsl.enable = true; 124 - users.wsl = { }; 139 + den.hosts.x86_64-linux.laptop = { 140 + users.alice = { }; 141 + gpu = "nvidia"; # custom attribute, accessible in aspects via host.gpu 125 142 }; 126 143 ``` 127 144 128 - Custom nixpkgs instance, home-manager osConfig and output attribute: 145 + Access custom attributes in aspects: 129 146 130 147 ```nix 131 - den.homes.x86_64-linux.tux-at-igloo = { 132 - aspect = "tux"; # Use den.aspects.tux for configuration 133 - intoAttr = [ "homeConfigurations" "tux@igloo" ]; 134 - instantiate = { pkgs, modules }: 135 - inputs.home-manager-stable.lib.homeManagerConfiguration { 136 - inherit modules; 137 - pkgs = inputs.self.nixpkgs-stable.legacyPackages.x86_64-linux; 138 - extraSpecialArgs.osConfig = inputs.self.nixosConfigurations.igloo.config; 139 - }; 140 - }; 148 + den.aspects.laptop.includes = [ 149 + ({ host, ... }: lib.optionalAttrs (host ? gpu) { 150 + nixos.hardware.nvidia.enable = true; 151 + }) 152 + ]; 141 153 ``` 142 -
+106 -73
docs/src/content/docs/guides/home-manager.mdx
··· 1 1 --- 2 - title: Homes Integration (hm / hjem / nix-maid, etc) 3 - description: Integrate Home-Manager into NixOS/Darwin hosts or use standalone. 2 + title: Homes Integration 3 + description: Home Manager, hjem, and nix-maid setup with Den. 4 4 --- 5 - 6 5 7 6 import { Aside } from '@astrojs/starlight/components'; 8 7 9 - <Aside title="Use the Source, Luke" icon="github"> 8 + 9 + <Aside title="Source" icon="github"> 10 10 [`hm-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-os.nix) · [`hm-integration.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/hm-integration.nix) 11 11 12 12 [`hjem-os.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/hjem/hjem-os.nix) · [`hjem-integration.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides/hjem/hjem-integration.nix) ··· 15 15 16 16 </Aside> 17 17 18 - Den supports different Nix libraries for managing user home files. 19 - All of them have Nix classes implemented using `den.provides.forward` 20 - and you can create custom home-like classes following the same pattern. 18 + ## Home Manager 19 + 20 + ### Enabling on Homes 21 21 22 - - `user` class. NixOS and nix-Darwin builtin `users.users.<userName>` submodule. 23 - - `homeManager` class. Enabled via `host.home-manager.enable` 24 - - `hjem` class. Enabled via `host.hjem.enable` 25 - - `maid` class. Enabled via `host.nix-maid.enable`. 22 + All Home integrations are opt-in and must be enabled explicitly. 26 23 27 - Class names follow the Nix class used by the module system of these projects 28 - not the project name, e.g: [`homeManager`](https://github.com/nix-community/home-manager/blob/f140aa04d7d14f8a50ab27f3691b5766b17ae961/modules/default.nix#L32). 24 + ```nix 25 + # Per user 26 + den.hosts.x86_64-linux.igloo.users.tux.classes = [ "homeManager" "hjem" ]; 29 27 30 - Except for the `user` class which is always enabled, other home classes 31 - are opt-in features, enabled via the host configuration: 28 + # Globally for all users 29 + den.base.user.classes = [ "homeManager" ]; 30 + ``` 32 31 33 - ```nix 34 - # per host: 35 - den.hosts.x86_64-linux.igloo.home-manager = { 36 - enable = true; 32 + Home integration contexts, like `den.ctx.hm-host` only activate when 33 + at least one host user has `homeManager` in their `classes`. 34 + When true, the integration imports the HomeManager OS module, 35 + and forwards each user's `homeManager` class into `home-manager.users.<userName>`. 37 36 38 - # optionally with custom stable/unstable HM input 39 - module = inputs.home-manager-unstable.nixosModules.default; 40 - }; 37 + Same details regarding `home-manager` apply to other home types like `hjem` and `maid`. 41 38 42 - # Globally (or conditionally on host) 43 - den.base.host = { host, ... }: { 44 - home-manager.enable = host.name == "igloo"; 45 - }; 39 + ### Requirements 40 + 41 + - `inputs.home-manager` must exist in your flake inputs or have custom `host.home-manager.module`. 42 + - At least one user must have `homeManager` in their `classes`. 43 + 44 + ### How it Works 45 + 46 + ```mermaid 47 + flowchart TD 48 + host["den.ctx.host"] -->|"into.hm-host"| hmhost["den.ctx.hm-host"] 49 + hmhost -->|"imports HM module"| mod["home-manager.nixosModules"] 50 + hmhost -->|"into.hm-user (per user)"| hmuser["den.ctx.hm-user"] 51 + hmuser -->|"forward homeManager class"| target["home-manager.users.alice"] 46 52 ``` 47 53 48 - ## Multi Home support. 54 + 1. `hm-os.nix` detects hosts with HM-enabled users and supported OS class. 55 + 2. It produces `den.ctx.hm-host`, which imports the HM OS-level module. 56 + 3. `hm-integration.nix` creates `den.ctx.hm-user` per HM user, forwarding 57 + the `homeManager` class into `home-manager.users.<userName>`. 58 + 59 + ### Configuring Home Manager 60 + 61 + ```nix 62 + den.aspects.alice = { 63 + homeManager = { pkgs, ... }: { 64 + home.packages = [ pkgs.htop ]; 65 + programs.git.enable = true; 66 + }; 67 + }; 68 + ``` 49 69 50 - User can choose to manage some files with home-manager instead or in 51 - addition to nix-maid and hjem. For example, when migrating away from home-manager into other libraries, you might want to keep your stuff working as you move progressively. 70 + The `homeManager` class contents are forwarded to the OS-level 71 + `home-manager.users.alice` automatically. 52 72 53 - Users define a `<user>.classes = [ "homeManager" ]` (default) where they 54 - explicitly enable the different home libraries they use. 73 + ### Custom HM Module 55 74 56 - ## Host-Managed Users 75 + Override the HM module per host if needed: 57 76 58 77 ```nix 59 - den.hosts.x86_64-linux.igloo.users.tux = { }; 78 + den.hosts.x86_64-linux.laptop = { 79 + users.vic.classes = [ "home-manager" ]; 80 + home-manager.module = inputs.home-manager-unstable.nixosModules.home-manager; 81 + }; 82 + ``` 60 83 61 - # host meta configuration (capabilities), HM always enabled. 62 - den.base.host.home-manager.enable = lib.mkDefault true; 84 + ## Standalone Homes 63 85 64 - # homeManager default settings 65 - den.default.homeManager.home.stateVersion = lib.mkDefault "25.11"; 86 + For machines without root access: 66 87 67 - # NixOS/Nix-Darwin user class. 68 - den.aspects.tux.user.description = "The Penguin"; 88 + ```nix 89 + den.homes.x86_64-linux.alice = { }; 90 + ``` 69 91 70 - # homeManager class 71 - den.aspects.tux.homeManager.programs.vim.enable = true; 72 - ``` 92 + This produces `flake.homeConfigurations.alice`, built with 93 + `inputs.home-manager.lib.homeManagerConfiguration`. 73 94 74 - User description forwarded into `igloo.users.users.tux.description`. 75 - The vim config is forwarded into `igloo.home-manager.users.tux`. 95 + ## hjem 76 96 77 - ## Standalone Home-Manager 97 + [hjem](https://github.com/feel-co/hjem) is an alternative, lightweight home environment manager. 78 98 79 - For Home-Manager without an OS host: 99 + ### Enabling 80 100 81 101 ```nix 82 - den.homes.aarch64-darwin.vic = { }; 83 - den.default.homeManager.home.stateVersion = "25.11"; 84 - den.aspects.vic.homeManager.programs.fish.enable = true; 102 + # Per host 103 + den.hosts.x86_64-linux.laptop = { 104 + users.alice.classes = [ "hjem" ]; 105 + }; 106 + 107 + # On all hosts 108 + den.base.host.hjem.enable = true; 85 109 ``` 86 110 87 - Build with `home-manager switch --flake .#vic`. 111 + ### Requirements 88 112 89 - ## Context types for Home enabled hosts. 113 + - `inputs.hjem` must exist. 114 + - Users must have `hjem` in their `classes`. 90 115 91 - Each Home integration in Den defines a context 92 - transformation from `den.ctx.host -> den.ctx.${name}-host`. 116 + ### Using 117 + 118 + ```nix 119 + den.aspects.alice.hjem = { }; 120 + ``` 93 121 94 - For example, `den.ctx.hm-host` is the aspect for Home-Manager enabled hosts. 95 - Similarly `den.ctx.maid-host` and `den.ctx.hjem-host`. 122 + ## nix-maid 96 123 97 - ## useGlobalPkgs at Home Manager enabled hosts 124 + [nix-maid](https://github.com/nix-maid) is another user-environment manager for NixOS. 98 125 99 - Configure a NixOS option only for Home-Manager hosts 100 - so they can use the host's nixpkgs: 126 + ### Enabling 101 127 102 128 ```nix 103 - den.ctx.hm-host.nixos.home-manager.useGlobalPkgs = true; 129 + den.hosts.x86_64-linux.laptop = { 130 + users.alice.classes = [ "maid" ]; 131 + }; 104 132 ``` 105 133 106 - This only activates for hosts that actually have Home-Manager users. 107 - Hosts without Home-Manager users are unaffected. 134 + ### Requirements 108 135 109 - ## Standalone with osConfig 136 + - `inputs.nix-maid` must exist. 137 + - Host class must be `"nixos"`. 138 + - Users must have `maid` in their `classes`. 110 139 111 - Access NixOS config from standalone Home-Manager: 140 + ### Using 112 141 113 142 ```nix 114 - den.homes.x86_64-linux.pingu = { 115 - instantiate = { pkgs, modules }: 116 - inputs.home-manager.lib.homeManagerConfiguration { 117 - inherit pkgs modules; 118 - extraSpecialArgs.osConfig = 119 - config.flake.nixosConfigurations.igloo.config; 120 - }; 143 + den.aspects.alice.maid = { 144 + # nix-maid configuration 121 145 }; 146 + ``` 122 147 123 - den.aspects.pingu.homeManager = { osConfig, ... }: { 124 - programs.emacs.enable = osConfig.programs.vim.enable; 148 + ## Multiple User Environments 149 + 150 + A user can participate in multiple environments: 151 + 152 + ```nix 153 + den.hosts.x86_64-linux.laptop = { 154 + users.alice.classes = [ "homeManager" "hjem" ]; 155 + home-manager.enable = true; 156 + hjem.enable = true; 125 157 }; 126 158 ``` 127 159 128 - 160 + Both `homeManager` and `hjem` configurations from `den.aspects.alice` will 161 + be forwarded to their respective targets.
-101
docs/src/content/docs/guides/migrate.md
··· 1 - --- 2 - title: Migrate to Den 3 - description: Incrementally adopt Den in existing Nix configurations. 4 - --- 5 - 6 - ## Start Small 7 - 8 - Den can be adopted incrementally. You don't need to rewrite your entire 9 - configuration — start by adding one Den-managed host alongside your 10 - existing setup. 11 - 12 - ## From Flake-Parts Dendritic 13 - 14 - If you're already using `flake.modules`, migration is direct: 15 - 16 - ```nix 17 - # Before (flake-parts dendritic) 18 - flake.modules.nixos.desktop = { ... }; 19 - flake.modules.homeManager.games = { ... }; 20 - 21 - # After (Den) 22 - den.aspects.desktop.nixos = { ... }; 23 - den.aspects.games.homeManager = { ... }; 24 - ``` 25 - 26 - ## Import Existing Modules 27 - 28 - You don't need to convert all modules. Import them directly: 29 - 30 - ```nix 31 - { inputs, ... }: { 32 - den.aspects.desktop.nixos.imports = [ 33 - inputs.disko.nixosModules.disko 34 - inputs.self.modules.nixos.desktop # existing module 35 - ]; 36 - } 37 - ``` 38 - 39 - ## Mix Den with Existing nixosSystem 40 - 41 - Use `mainModule` to integrate Den into existing configurations: 42 - 43 - ```nix 44 - let 45 - denCfg = (lib.evalModules { 46 - modules = [ (import-tree ./modules) ]; 47 - specialArgs = { inherit inputs; }; 48 - }).config; 49 - in 50 - lib.nixosSystem { 51 - modules = [ 52 - ./hardware-configuration.nix # your existing modules 53 - ./networking.nix 54 - denCfg.den.hosts.x86_64-linux.igloo.mainModule # Den modules 55 - ]; 56 - } 57 - ``` 58 - 59 - ## Use import-tree for Gradual Migration 60 - 61 - The [`import-tree`](/reference/batteries/#import-tree) battery auto-imports files by class directory: 62 - 63 - ``` 64 - non-dendritic/ 65 - hosts/ 66 - my-laptop/ 67 - _nixos/ 68 - hardware.nix 69 - networking.nix 70 - _homeManager/ 71 - shell.nix 72 - ``` 73 - 74 - ```nix 75 - den.ctx.host.includes = [ 76 - (den._.import-tree._.host ./non-dendritic/hosts) 77 - ]; 78 - ``` 79 - 80 - Files in `_nixos/` import as NixOS modules, `_homeManager/` as HM modules. 81 - 82 - ## Access Den Configurations Directly 83 - 84 - Den's outputs are standard Nix configurations: 85 - 86 - ```nix 87 - # These are normal nixosSystem / homeManagerConfiguration results 88 - config.flake.nixosConfigurations.igloo 89 - config.flake.homeConfigurations.tux 90 - ``` 91 - 92 - Expose them directly in your flake outputs alongside existing ones. 93 - 94 - ## Recommended Migration Path 95 - 96 - 1. Add Den inputs to your flake 97 - 2. Create a `modules/` directory with `hosts.nix` 98 - 3. Add one host with `den.hosts` 99 - 4. Move one aspect at a time from existing modules 100 - 5. Use `import-tree` for files you haven't converted yet 101 - 6. Gradually expand Den-managed aspects
+119
docs/src/content/docs/guides/migrate.mdx
··· 1 + --- 2 + title: Migrate to Den 3 + description: Incremental migration from existing NixOS/Darwin setups. 4 + --- 5 + 6 + import { Steps } from '@astrojs/starlight/components'; 7 + 8 + ## Strategy 9 + 10 + Migration to Den is incremental. You do not need to rewrite everything at once. 11 + 12 + <Steps> 13 + 14 + 1. Add Den as Input 15 + 16 + Add Den to your flake and import the flake module: 17 + 18 + ```nix 19 + { 20 + inputs.den.url = "github:vic/den"; 21 + inputs.import-tree.url = "github:vic/import-tree"; 22 + inputs.flake-aspects.url = "github:vic/flake-aspects"; 23 + 24 + outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } 25 + (inputs.import-tree ./modules); 26 + } 27 + ``` 28 + 29 + ```nix 30 + # modules/den.nix 31 + { inputs, ... }: { 32 + imports = [ inputs.den.flakeModule ]; 33 + } 34 + ``` 35 + 36 + 2. Declare Hosts 37 + 38 + Move your host declarations into `den.hosts`: 39 + 40 + ```nix 41 + { 42 + den.hosts.x86_64-linux.laptop.users.alice = { }; 43 + } 44 + ``` 45 + 46 + 3. Import Existing Modules 47 + 48 + Use `den.provides.import-tree` to load your existing non-dendritic modules: 49 + 50 + ```nix 51 + # modules/legacy.nix 52 + { den, ... }: { 53 + den.ctx.host.includes = [ 54 + (den.provides.import-tree._.host ./hosts) 55 + ]; 56 + den.ctx.user.includes = [ 57 + (den.provides.import-tree._.user ./users) 58 + ]; 59 + } 60 + ``` 61 + 62 + With this directory structure: 63 + 64 + ``` 65 + hosts/ 66 + laptop/ 67 + _nixos/ 68 + hardware.nix 69 + networking.nix 70 + _homeManager/ 71 + shell.nix 72 + users/ 73 + alice/ 74 + _homeManager/ 75 + git.nix 76 + _nixos/ 77 + groups.nix 78 + ``` 79 + 80 + Files under `_nixos/` are imported as NixOS modules, `_homeManager/` as 81 + Home Manager modules, etc. This requires `inputs.import-tree`. 82 + 83 + 4. Extract Aspects 84 + 85 + Gradually extract features from your legacy modules into Den aspects: 86 + 87 + ```nix 88 + # modules/dev-tools.nix 89 + { 90 + den.aspects.dev-tools = { 91 + nixos = { pkgs, ... }: { 92 + environment.systemPackages = with pkgs; [ git vim tmux ]; 93 + }; 94 + homeManager.programs.git.enable = true; 95 + }; 96 + } 97 + ``` 98 + 99 + ```nix 100 + # modules/laptop.nix 101 + { den, ... }: { 102 + den.aspects.laptop.includes = [ den.aspects.dev-tools ]; 103 + } 104 + ``` 105 + 106 + 5. Remove Legacy 107 + 108 + As aspects replace legacy modules, remove the corresponding files from 109 + `hosts/` and `users/`. Eventually remove `den.provides.import-tree` usage. 110 + 111 + </Steps> 112 + 113 + ## Tips 114 + 115 + - **Start with one host.** Migrate a single machine first to learn the pattern. 116 + - **Keep legacy working.** `import-tree` loads your existing files alongside 117 + Den aspects -- they coexist without conflicts. 118 + - **Use batteries.** Replace manual user/shell/HM setup with `den.provides.*`. 119 + - **Test with VM.** Use `nix run .#vm` to validate changes before applying to hardware.
+71 -71
docs/src/content/docs/guides/namespaces.mdx
··· 1 1 --- 2 2 title: Share with Namespaces 3 - description: Share and consume aspect libraries across repositories. 3 + description: Publish and consume aspect libraries across flakes. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`nix/namespace.nix`](https://github.com/vic/den/blob/main/nix/namespace.nix)</Aside> 9 - 10 - ## What Are Namespaces? 11 - 12 - Namespaces let you organize aspects into named collections that can be 13 - shared across flakes and consumed by others. 8 + <Aside title="Source" icon="github"> 9 + [`nix/namespace.nix`](https://github.com/vic/den/blob/main/nix/namespace.nix) 10 + </Aside> 14 11 15 - This is unlike your `den.aspects.<name>` namespace which is private, 16 - because you wont likely want to share your user/host configurations. 12 + ## What are Namespaces 17 13 18 - Namespaces are for re-usable aspects you are willing to share with others. 14 + A **namespace** creates a scoped aspect library under `den.ful.<name>`. 15 + Namespaces can be: 16 + - **Local**: defined in your flake, consumed internally. 17 + - **Exported**: exposed via `flake.denful.<name>` for other flakes to consume. 18 + - **Imported**: merged from upstream flakes into your local `den.ful`. 19 19 20 - ## Define a Local Namespace 20 + ## Creating a Namespace 21 21 22 22 ```nix 23 - { inputs, ... }: { 24 - imports = [ (inputs.den.namespace "ns" false) ]; 25 - ns.tools.nixos.programs.vim.enable = true; 23 + # modules/namespace.nix 24 + { inputs, den, ... }: { 25 + # Create "my" namespace (not exported to flake outputs) 26 + imports = [ (inputs.den.namespace "my" false) ]; 27 + 28 + # Or create and export "eg" namespace 29 + imports = [ (inputs.den.namespace "eg" true) ]; 26 30 } 27 31 ``` 28 32 29 - The first argument is the aspect namespace name. 30 - It will be provided as module argument: `{ lib, ns, ... }` 31 - so you can access aspects from the namespace. 33 + This creates: 34 + - `den.ful.eg` -- the namespace attrset (aspects type). 35 + - `eg` -- a module argument alias to `den.ful.eg`. 36 + - `flake.denful.eg` -- flake output (if exported). 32 37 33 - The second argument controls output: 34 - - `false` — local only, not exposed as flake output 35 - - `true` — exposed at `flake.denful.ns` 36 - - A list of sources — merge from external inputs 37 - 38 - ## Consume Remote Namespaces 38 + ## Populating a Namespace 39 39 40 - Import aspects from another flake's `flake.denful.provider` output: 40 + Define aspects under the namespace using any module: 41 41 42 42 ```nix 43 - { inputs, ... }: { 44 - imports = [ 45 - (inputs.den.namespace "provider" [ true inputs.other-flake ]) 46 - ]; 47 - 48 - den.aspects.igloo.includes = [ provider.tools._.editors ]; 43 + # modules/aspects/vim.nix 44 + { 45 + eg.vim = { 46 + homeManager.programs.vim.enable = true; 47 + }; 49 48 } 50 49 ``` 51 50 52 - Multiple sources are merged by the module system. 53 - 54 - ## Nested Provides in Namespaces 55 - 56 - Namespaces support the full aspect tree with `provides`: 57 - 58 51 ```nix 59 - ns.root.provides.branch.provides.leaf.nixos.truth = true; 60 - # access via: 61 - ns.root._.branch._.leaf 52 + # modules/aspects/desktop.nix 53 + { eg, ... }: { 54 + eg.desktop = { 55 + includes = [ eg.vim ]; 56 + nixos.services.xserver.enable = true; 57 + }; 58 + } 62 59 ``` 63 60 64 - ## Expose as Flake Output 61 + ## Using Namespaced Aspects 65 62 66 - When the second argument is `true` (or a list containing `true`), 67 - the namespace appears at `config.flake.denful.<name>`: 63 + Reference them by their namespace: 68 64 69 65 ```nix 70 - imports = [ (inputs.den.namespace "ns" true) ]; 71 - ns.foo.nixos.truth = true; 72 - # available at config.flake.denful.ns 66 + { eg, ... }: { 67 + den.aspects.laptop.includes = [ 68 + eg.desktop 69 + eg.vim 70 + ]; 71 + } 73 72 ``` 74 73 75 - Other flakes can then consume it: 74 + ## Importing from Upstream 75 + 76 + Merge aspects from other flakes: 76 77 77 78 ```nix 78 - inputs.your-flake.denful.ns 79 + # modules/namespace.nix 80 + { inputs, ... }: { 81 + # Import "shared" namespace from upstream, merging with local definitions 82 + imports = [ (inputs.den.namespace "shared" [ inputs.team-config ]) ]; 83 + } 79 84 ``` 80 85 81 - ## Merge Multiple Sources 86 + The namespace function accepts: 87 + - A **boolean** (`true`/`false`) for local/exported namespaces. 88 + - A **list of sources** to merge from upstream flakes. 89 + Each source's `flake.denful.<name>` is merged into `den.ful.<name>`. 82 90 83 - Combine local, remote, and output in one namespace: 91 + ## Enabling Angle Brackets 92 + 93 + When using namespaces, enable angle bracket syntax for terser references: 84 94 85 95 ```nix 86 - imports = [ 87 - (inputs.den.namespace "ns" [ 88 - inputs.sourceA 89 - inputs.sourceB 90 - true # also expose as output 91 - ]) 92 - ]; 93 - 94 - ns.gear.nixos.data = [ "local" ]; 95 - # merges with sourceA and sourceB's denful.ns.gear 96 + { den, ... }: { 97 + _module.args.__findFile = den.lib.__findFile; 98 + } 96 99 ``` 97 100 98 - ## Use with Angle Brackets 99 - 100 - When `__findFile` is in scope, namespace aspects are accessible via 101 - angle brackets: 101 + Then reference deep aspects with `<namespace/path>`: 102 102 103 103 ```nix 104 - { __findFile, ... }: { 105 - _module.args.__findFile = den.lib.__findFile; 106 - 107 - den.aspects.igloo.includes = [ <ns/tools> ]; 108 - } 104 + den.aspects.laptop.includes = [ <eg/desktop> ]; 109 105 ``` 110 106 111 - ## Real-World: denful 107 + ## Architecture 112 108 113 - [denful](https://github.com/vic/denful) is a (WIP) community aspect distribution 114 - built on Den namespaces — a lazyvim-like approach to Nix configurations. 109 + ```mermaid 110 + flowchart LR 111 + upstream["upstream flake"] -->|"flake.denful.shared"| merge["den.ful.shared"] 112 + local["local modules"] -->|"shared.vim = ..."| merge 113 + merge --> consumer["den.aspects.*.includes"] 114 + ```
+83 -106
docs/src/content/docs/index.mdx
··· 1 1 --- 2 2 title: Context-aware Dendritic Nix 3 - description: Context-driven, aspect-oriented, cross-class Nix configurations. 3 + description: Aspect-oriented, context-driven Nix configurations for NixOS, nix-darwin, and home-manager. 4 4 template: splash 5 5 hero: 6 - tagline: Context-driven, aspect-oriented, cross-class Nix configurations 6 + tagline: Aspect-oriented, context-driven Nix configurations 7 7 image: 8 8 html: | 9 9 <img width="400" height="400" src="https://github.com/user-attachments/assets/af9c9bca-ab8b-4682-8678-31a70d510bbb" /> ··· 29 29 variant: minimal 30 30 --- 31 31 32 - import { LinkButton, Card, CardGrid } from '@astrojs/starlight/components'; 32 + import { Steps, LinkButton, Card, CardGrid } from '@astrojs/starlight/components'; 33 33 34 34 ## Testimonials 35 35 36 36 > Den takes the Dendritic pattern to a whole new level, and I cannot imagine going back. 37 - > @adda - Very early Den adopter after using Dendritic flake-parts and Unify. 37 + > -- `@adda` - Very early Den adopter after using Dendritic flake-parts and Unify. 38 + 38 39 39 40 > I'm super impressed with den so far, I'm excited to try out some new patterns that Unify couldn't easily do. 40 - > @quasigod - Unify dendritic-framework author on adopting Den. [\[repo\]](https://tangled.org/quasigod.xyz/nixconfig) 41 + > -- `@quasigod` - Unify dendritic-framework author on adopting Den. [\[repo\]](https://tangled.org/quasigod.xyz/nixconfig) 42 + 41 43 42 44 > Massive work you did here! 43 - > @drupol - Author of "Flipping the Configuration Matrix" Dendritic blog post. [\[repo\]](https://github.com/drupol/infra/tree/push-woqtkxkpstro) 45 + > -- `@drupol` - Author of "Flipping the Configuration Matrix" Dendritic blog post. [\[repo\]](https://github.com/drupol/infra/tree/push-woqtkxkpstro) 46 + 44 47 45 48 > Thanks for the awesome library and the support for non-flakes... it’s positively brilliant!. I really hope this gets wider adoption. 46 - > @vczf - At den matrix channel. 49 + > -- `@vczf` - At den matrix channel. 47 50 48 51 49 52 ## What is Den 50 53 51 - At its core, Den is a **library** for activating Nix-modules configuration aspects via context transformations. 54 + 55 + At its core, Den is a **library** for activating Nix configuration aspects via context transformations. 52 56 53 57 On top of the library, Den provides a **framework** for the common case 54 - of NixOS/Darwin/Home-Manager. 58 + of NixOS/nix-Darwin/Home-Manager. 55 59 56 60 Den embraces your Nix choices and does not impose itself. All parts of Den are optional and replaceable. 57 61 58 - ## Two Core Principles 59 - 60 - Den builds on [flake-aspects](https://github.com/vic/flake-aspects)' **parametric aspects**, 61 - and provides a declarative **context-pipeline** as cutting-points for these aspects. 62 - 63 - <CardGrid> 64 - <Card title="Context Transformation" icon="random"> 65 - Data flows through a declarative [pipeline](/explanation/context-pipeline/) of 66 - [contexts](/explanation/context-system/). Each stage enriches the context — from 67 - host definitions through user enumeration to domain-specific requirements. 68 - </Card> 69 - <Card title="Context-Aware Aspects" icon="puzzle"> 70 - [Aspects](/explanation/aspects/) are composable bundles of cross-class Nix configs. 71 - They inspect context to produce conditional, [parametric](/explanation/parametric/) 72 - configurations — and are activated by a matching context. 73 - </Card> 74 - </CardGrid> 75 - 76 - 77 - ### 1. Context Transformation 62 + ## How Den works 78 63 79 - Data flows through a declarative pipeline. You declare the entities (data) that exists in your universe, Den transforms them into progressively richer contexts to which aspects are attached, providing configuration for each context: 64 + Den uses a Dendritic Nix configuration model. Features are organized using [flake-aspects](https://github.com/vic/flake-aspects) -- composable attrsets with per-class configurations and 65 + a dependency graph (`provides/includes`). Den's **context pipeline** determines which aspects apply to which targets collecting the configurations they contribute to each context transformation stage, ultimately producing a single, unified system configuration. 80 66 67 + <div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px;"> 68 + <div> 81 69 ```mermaid 82 70 graph LR 83 71 H["den.ctx.host {host}"] --> U["den.ctx.user {host, user} (for each user)"] 84 72 U --> HM["aspect provides homeManager/hjeim class"] 85 73 H --> OS["aspect provides nixos/darwin class"] 86 74 ``` 87 - 88 - Each stage of the pipeline is a [**context type**](/explanation/context-system/) defined in `den.ctx`. Context types 89 - declare how to find [aspects](/explanation/aspects/), how to transform into other contexts, and which 90 - [parametric](/explanation/parametric/) includes to activate. 91 - 92 - ### 2. Context-Aware Aspects 93 - 94 - Aspects are composable bundles of multi-class Nix modules that inspect their context parameters to decide 95 - what to produce. A function taking `{ host, ... }` only runs when a host context 96 - exists. A function taking `{ host, user, ... }` runs once per user on each host. 97 - 98 - Functions that require parameters not present in the current context are 99 - not included. Den introduces pattern matching on context shape. 100 - 101 - 102 - 103 - ## Den as a Library 104 - 105 - ```nix 106 - # Use Den API -- Build the dependencies graph for NixOS hosts 107 - aspect = den.ctx.host { host = den.hosts.x86_64-linux.igloo; }; 108 - 109 - # Use flake-aspects API -- We enter the NixOS domain by resolving for the "nixos" class. 110 - nixosModule = aspect.resolve { class = "nixos"; }; 111 - 112 - # Use NixOS API -- Instantiate using nixosSystem with the resolved module. 113 - nixosConfigurations.igloo = lib.nixosSystem { modules = [ nixosModule ]; }; 75 + </div> 76 + <div> 77 + ```mermaid 78 + flowchart BT 79 + subgraph "**den.aspects.dev-tools**" 80 + n["nixos"] 81 + h["homeManager"] 82 + d["darwin"] 83 + end 84 + n --> server["server (NixOS)"] 85 + n --> laptop["laptop (NixOS)"] 86 + d --> mac["mac (Darwin)"] 87 + h --> laptop 88 + h --> mac 89 + h --> standalone["alice (standalone HM)"] 114 90 ``` 91 + </div> 92 + </div> 115 93 116 - This same pattern works for any class — create your own context transformation graphs and replace `"nixos"` with 117 - `"darwin"`, `"systemManager"`, `"terranix"` or any custom class name. 118 94 95 + ## Den as a *library* 119 96 120 97 <CardGrid> 121 - <Card title="Any Nix Class" icon="nix"> 122 - NixOS, Darwin, Home-Manager, Terraform, NixVim — Den 123 - works with [anything configurable through Nix](/explanation/library-vs-framework/). 98 + <Card title="Any Nix Configuration Class" icon="puzzle"> 99 + NixOS, Darwin, system-manager, Terraform, 100 + or [custom classes](/guides/custom-classes/) 101 + 102 + Den works with anything configurable through Nix modules and any 103 + topology you can describe with Nix data and function transformations. 124 104 </Card> 125 105 <Card title="Context Pipeline" icon="right-arrow"> 126 - Define custom [context types](/reference/ctx/) with `den.ctx`. Declarative 127 - [transformations](/explanation/context-pipeline/) propagate data through your config graph. 106 + Declarative [context types](/reference/ctx/) with `den.ctx`. 107 + 108 + Each context stage can [transform](/explanation/context-pipeline/) into other contexts, 109 + using a type-driven design, ensuring only valid contexts exist via 110 + principles like [parse don't validate](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/) 128 111 </Card> 129 112 <Card title="No Lock-in" icon="open-book"> 130 - Works [with flakes, without flakes](/guides/no-flakes/), with flake-parts, or 131 - standalone. Den plays well with your existing Nix choices. And integrates with your existing infra. 113 + Works with flake-parts, without flake-parts, or [without flakes at all](/tutorials/noflake/). 114 + 115 + Den never uses `lib.evalModules` directly, but provides APIs and modules for use with your favorite Nix module system. 132 116 </Card> 133 117 <Card title="Sharable Aspects" icon="star"> 134 - Re-usablity is one of the goals of Den. Allowing people to create truly-generic configuration modules. 135 - [Namespaces](/guides/namespaces/) let you publish and consume aspect libraries 136 - across repositories and flakes. 118 + [Namespaces](/guides/namespaces/) let you publish and consume aspect 119 + libraries across flakes or non-flakes. 120 + 121 + Den is social, trying to bring re-usability across stable and unstable Nix boundaries. 122 + No Flakes mandated. 137 123 </Card> 138 124 </CardGrid> 139 125 140 - ## Den as a Framework 126 + The following three lines is how a NixOS configuration is built. 127 + The very same process can be used for other Nix configuration domains outside 128 + of NixOS. 141 129 142 130 ```nix 143 - # use in flake-parts or any lib.evalModules where den.nixModule is imported 144 - { den, ... }: { 131 + # Use Den API -- Context transformations happen here, nothing is configured yet. 132 + aspect = den.ctx.host { host = den.hosts.x86_64-linux.igloo; }; 145 133 146 - # declare your hosts, users and homes 147 - den.x86_64-linux.hosts.igloo.users.tux = { }; 134 + # Use flake-aspects API -- We enter the NixOS domain by resolving for the "nixos" class. 135 + nixosModule = aspect.resolve { class = "nixos"; }; 148 136 149 - # attach configurations via aspects 150 - den.igloo.nixos.networking.hostName = "warm-home"; 137 + # Use NixOS API -- Instantiate using nixosSystem with the resolved module. 138 + nixosConfigurations.igloo = lib.nixosSystem { modules = [ nixosModule ]; }; 139 + ``` 151 140 152 - # user aspects can configure its home and host environment 153 - den.tux.homeManager = { pkgs, ... }: { home.packages = [ pkgs.cowsay ]; }; 154 - den.tux.nixos.users.users.tux.desscription = "cute pinguin"; 155 141 156 - # include other re-usable aspects of yours or den batteries 157 - den.tux.includes = [ (den.provides.user-shell "fish") ]; 142 + ## Den as a _framework_. 158 143 159 - # generic config for all hosts, users, homes. 160 - den.default.nixos.system.stateVersion = "25.11"; 161 - den.default.homeManager.home.stateVersion = "25.11"; 162 - den.default.includes = [ den._.inputs' ]; 144 + Built on top of `den.lib`, Den provides a framework with ready-made facilities for 145 + NixOS/nix-Darwin/homes configurations. 163 146 164 - } 147 + ```mermaid 148 + flowchart LR 149 + Schema["den.hosts / den.homes"] --> Ctx["den.ctx pipeline"] 150 + Aspects["den.aspects"] --> Ctx 151 + Batteries["den.provides"] --> Ctx 152 + Ctx --> Resolve["Resolution"] 153 + Resolve --> Out["nixos / darwin / homeConfigurations"] 165 154 ``` 166 155 167 - <CardGrid> 168 - <Card title="Declare Systems" icon="laptop"> 169 - One-liner [host and user](/guides/declare-hosts/) definitions with 170 - [freeform schemas](/reference/schema/). NixOS, Darwin, Home-Manager — 171 - all from a single source. 172 - </Card> 173 - <Card title="Bidirectional" icon="right-arrow"> 174 - Hosts configure their users. Users contribute to their 175 - hosts. [Aspects flow in both directions](/guides/configure-aspects/) automatically. 176 - </Card> 177 - <Card title="Batteries Included" icon="add-document"> 178 - Opt-in [aspects for common tasks](/guides/batteries/): define-user, primary-user, 179 - user-shell, unfree packages, import-tree, and more. 180 - </Card> 181 - <Card title="Community" icon="heart"> 182 - Share configurations through [namespaces](/guides/namespaces/). Real-world setups 183 - and a growing ecosystem at [denful](https://github.com/vic/denful) (wip). 184 - </Card> 185 - </CardGrid> 156 + <Steps> 157 + 1. **Schema** -- `den.hosts` and `den.homes` declare machines, users, and their properties with `den.base` modules and extensible freeform types. 158 + 2. **Aspects** -- `den.aspects.*` bundles per-class configs (`nixos`, `darwin`, `homeManager`, or any custom class) with `.includes` and `.provides` forming a DAG. 159 + 3. **Context pipeline** -- `den.ctx` transforms schema entries into context pairs (`{host}`, `{host, user}`, `{home}`), walking `into.*` transitions for derived contexts like `hm-host`, `hm-user`, `wsl-host` or any other custom context stage. 160 + 4. **Resolution** -- Parametric dispatch via `__functor` argument introspection. Functions receive only contexts whose shape matches their parameters. 161 + 5. **Output** -- Each host/home is instantiated via `nixpkgs.lib.nixosSystem`, `darwin.lib.darwinSystem`, or `home-manager.lib.homeManagerConfiguration`. 162 + </Steps> 186 163 164 + <LinkButton icon="right-arrow" variant="primary" href="/explanation/core-principles">Learn More</LinkButton> 187 165 188 - Feel free to to **explore** the [codebase](https://github.com/vic/den), particularly our [included batteries](https://github.com/vic/den/tree/main/modules/aspects/provides) and [tests](https://github.com/vic/den/tree/main/templates/ci/modules/features) that serve as examples. 189 166 190 167 ## Den is made possible by amazing people 191 168
+42 -222
docs/src/content/docs/overview.mdx
··· 1 1 --- 2 2 title: Documentation Overview 3 - description: Everything Den 3 + description: Map of all Den documentation sections. 4 4 --- 5 5 6 - import { Card, CardGrid, LinkButton } from '@astrojs/starlight/components'; 6 + import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components'; 7 7 8 - ## Core Concepts 9 - 10 - <CardGrid> 11 - <Card title="Context Transformation" icon="rocket"> 12 - Declarative pipeline that flows configuration through named context types — host, user, home, and custom. 13 - <LinkButton href="/explanation/core-principles" variant="minimal" icon="right-arrow">Learn More</LinkButton> 14 - </Card> 15 - <Card title="Context-Aware Aspects" icon="puzzle"> 16 - Aspects are functions of context. A single aspect can target NixOS, Home-Manager, Darwin, and more — resolved automatically by context. 17 - <LinkButton href="/explanation/aspects" variant="minimal" icon="right-arrow">Learn More</LinkButton> 18 - </Card> 19 - <Card title="Context Pipeline" icon="list-format"> 20 - Stage-by-stage flow from host entry through user enumeration, HM detection, deduplication, and standalone homes. 21 - <LinkButton href="/explanation/context-pipeline" variant="minimal" icon="right-arrow">Learn More</LinkButton> 22 - </Card> 23 - <Card title="Context System" icon="setting"> 24 - Named context types with `den.ctx` schema — providers, includes, transformations, fan-out, and 10+ built-in context types. 25 - <LinkButton href="/explanation/context-system" variant="minimal" icon="right-arrow">Learn More</LinkButton> 26 - </Card> 27 - <Card title="Parametric Aspects" icon="seti:config"> 28 - `den.lib.parametric` with matching variants: `atLeast`, `exactly`, `fixedTo`, `expands`, `withOwn`, and `take` functions. 29 - <LinkButton href="/explanation/parametric" variant="minimal" icon="right-arrow">Learn More</LinkButton> 30 - </Card> 31 - <Card title="Library vs Framework" icon="open-book"> 32 - Den's dual nature — use the pure library for custom pipelines, or the framework for NixOS/Darwin/HM. All parts optional and replaceable. 33 - <LinkButton href="/explanation/library-vs-framework" variant="minimal" icon="right-arrow">Learn More</LinkButton> 34 - </Card> 35 - </CardGrid> 8 + ## Learn 36 9 37 - ## Configuration & Schema 10 + Understand the principles and mechanisms behind Den. 38 11 39 12 <CardGrid> 40 - <Card title="Declare Hosts & Users" icon="laptop"> 41 - Define hosts with class detection, custom attributes, multiple users, standalone HM, base modules, and custom instantiation. 42 - <LinkButton href="/guides/declare-hosts" variant="minimal" icon="right-arrow">Learn More</LinkButton> 43 - </Card> 44 - <Card title="Configure Aspects" icon="pencil"> 45 - Static host config, context-aware includes, bidirectional user/host config, conditional config, global defaults, and named sub-aspects. 46 - <LinkButton href="/guides/configure-aspects" variant="minimal" icon="right-arrow">Learn More</LinkButton> 47 - </Card> 48 - <Card title="Schema Reference" icon="document"> 49 - Entity schemas for `den.hosts`, `den.homes`, `den.base` — host options, user options, home options, class detection, and instantiation. 50 - <LinkButton href="/reference/schema" variant="minimal" icon="right-arrow">Learn More</LinkButton> 51 - </Card> 52 - <Card title="Output & Build" icon="seti:favicon"> 53 - How Den builds `nixosConfigurations`, `darwinConfigurations`, and `homeConfigurations`. Output placement, main module resolution. 54 - <LinkButton href="/reference/output" variant="minimal" icon="right-arrow">Learn More</LinkButton> 55 - </Card> 56 - <Card title="Aspects Reference" icon="seti:folder-config"> 57 - Aspect attribute table, automatic creation from `den.hosts`/`den.homes`, resolution via `flake-aspects`, `den.aspects` and `den.provides`. 58 - <LinkButton href="/reference/aspects" variant="minimal" icon="right-arrow">Learn More</LinkButton> 59 - </Card> 60 - <Card title="Context Reference" icon="seti:pipeline"> 61 - All built-in context types in detail: host, user, default, home, hm-host, hm-user, maid-host, maid-user, hjem-host, hjem-user. 62 - <LinkButton href="/reference/ctx" variant="minimal" icon="right-arrow">Learn More</LinkButton> 63 - </Card> 13 + <LinkCard title="Core Principles" href="/explanation/core-principles/" description="Dendritic design: features over hosts, aspects over modules." /> 14 + <LinkCard title="Context System" href="/explanation/context-system/" description="How den.ctx types define data flow between entities." /> 15 + <LinkCard title="Aspects & Functors" href="/explanation/aspects/" description="The __functor pattern and aspect structure." /> 16 + <LinkCard title="Parametric Aspects" href="/explanation/parametric/" description="Context-aware function dispatch via argument introspection." /> 17 + <LinkCard title="Context Pipeline" href="/explanation/context-pipeline/" description="How host/user/home contexts transform into NixOS/Darwin/HM modules." /> 18 + <LinkCard title="Library vs Framework" href="/explanation/library-vs-framework/" description="Using Den's core lib for any Nix domain." /> 64 19 </CardGrid> 65 20 66 - ## Home & User Management 21 + ## Cookbook 67 22 68 - <CardGrid> 69 - <Card title="Home Manager Integration" icon="seti:home"> 70 - Automatic HM detection, host-managed users, standalone HM, `useGlobalPkgs`, custom modules, `osConfig` access, and context hooks. 71 - <LinkButton href="/guides/home-manager" variant="minimal" icon="right-arrow">Learn More</LinkButton> 72 - </Card> 73 - <Card title="Custom Classes" icon="add-document"> 74 - Create new Nix classes with `den._.forward` — hjem, maid, containers, VMs, guarded forward with conditions. 75 - <LinkButton href="/guides/custom-classes" variant="minimal" icon="right-arrow">Learn More</LinkButton> 76 - </Card> 77 - <Card title="User Classes" icon="seti:users"> 78 - Default `homeManager` class, multiple user classes, per-class aspect resolution, OS user class forwarding. 79 - <LinkButton href="/tutorials/ci" variant="minimal" icon="right-arrow">Learn More</LinkButton> 80 - </Card> 81 - <Card title="Bidirectional Config" icon="random"> 82 - Host-owned config applies to all users; user-owned config applies to all hosts. Static, parametric, and function-based. 83 - <LinkButton href="/guides/configure-aspects" variant="minimal" icon="right-arrow">Learn More</LinkButton> 84 - </Card> 85 - <Card title="Standalone Homes" icon="seti:home"> 86 - Standalone `homeConfigurations` output — aspect config, custom username, `den.ctx.home` context, independent of any host. 87 - <LinkButton href="/guides/home-manager" variant="minimal" icon="right-arrow">Learn More</LinkButton> 88 - </Card> 89 - <Card title="Hjem & Maid Classes" icon="seti:folder-config"> 90 - Built-in hjem and maid user class support — forwarding, merge with NixOS, automatic class detection. 91 - <LinkButton href="/guides/custom-classes" variant="minimal" icon="right-arrow">Learn More</LinkButton> 92 - </Card> 93 - <Card title="OS User Class" icon="seti:users"> 94 - Forward user descriptions, access OS args like `pkgs`, mergeable options across user/host/NixOS boundaries. 95 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 96 - </Card> 97 - </CardGrid> 98 - 99 - ## Batteries Included 23 + Practical recipes for common tasks. 100 24 101 25 <CardGrid> 102 - <Card title="define-user" icon="seti:users"> 103 - `den._.define-user` — creates `isNormalUser`, sets home directory, works at user, host, or default level. 104 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 105 - </Card> 106 - <Card title="primary-user" icon="star"> 107 - `den._.primary-user` — adds wheel and networkmanager groups to the primary user. 108 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 109 - </Card> 110 - <Card title="user-shell" icon="seti:shell"> 111 - `den._.user-shell` — sets the user shell on both OS and Home-Manager simultaneously. 112 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 113 - </Card> 114 - <Card title="unfree" icon="approve-check"> 115 - `den._.unfree` — sets `allowUnfreePredicate` on both NixOS and Home-Manager. 116 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 117 - </Card> 118 - <Card title="tty-autologin" icon="seti:log"> 119 - `den._.tty-autologin` — creates a getty autologin service for the user. 120 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 121 - </Card> 122 - <Card title="import-tree" icon="seti:folder-src"> 123 - `den._.import-tree._.host` — auto-imports host directories based on class, handles missing dirs gracefully. 124 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 125 - </Card> 126 - <Card title="inputs' & self'" icon="seti:nix"> 127 - `den._.inputs'` and `den._.self'` — provide flake-parts special args (`inputs'`, `self'`) inside aspects. 128 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 129 - </Card> 130 - <Card title="forward" icon="right-arrow"> 131 - `den._.forward` — forward aspect config into custom Nix classes with optional guard conditions. 132 - <LinkButton href="/reference/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 133 - </Card> 134 - <Card title="Batteries Guide" icon="open-book"> 135 - Overview and usage guide for all built-in batteries — when to use each one, practical examples, and composition patterns. 136 - <LinkButton href="/guides/batteries" variant="minimal" icon="right-arrow">Learn More</LinkButton> 137 - </Card> 26 + <LinkCard title="Declare Hosts & Users" href="/guides/declare-hosts/" description="den.hosts, den.homes, users, and schema options." /> 27 + <LinkCard title="Configure Aspects" href="/guides/configure-aspects/" description="Owned configs, includes, provides, and defaults." /> 28 + <LinkCard title="Custom Nix Classes" href="/guides/custom-classes/" description="Create new classes via den.provides.forward." /> 29 + <LinkCard title="Homes Integration" href="/guides/home-manager/" description="Home Manager, hjem, and nix-maid setup." /> 30 + <LinkCard title="Use Batteries" href="/guides/batteries/" description="den.provides.* built-in aspects." /> 31 + <LinkCard title="Share with Namespaces" href="/guides/namespaces/" description="Publish and consume aspect libraries across flakes." /> 32 + <LinkCard title="Angle Brackets Syntax" href="/guides/angle-brackets/" description="<aspect/path> shorthand via __findFile." /> 33 + <LinkCard title="Migrate to Den" href="/guides/migrate/" description="Incremental migration from existing setups." /> 34 + <LinkCard title="Debug Configurations" href="/guides/debug/" description="REPL inspection, tracing, and troubleshooting." /> 138 35 </CardGrid> 139 36 140 - ## Library API 141 - 142 - <CardGrid> 143 - <Card title="den.lib Reference" icon="seti:code-search"> 144 - Full API: `parametric`, `take`, `canTake`, `aspects`, `isFn`, `owned`, `statics`, `isStatic`, `__findFile`. 145 - <LinkButton href="/reference/lib" variant="minimal" icon="right-arrow">Learn More</LinkButton> 146 - </Card> 147 - <Card title="Angle Brackets" icon="seti:bracket-dot"> 148 - Opt-in `<den/...>` shorthand — access `<den.lib>`, `<den/import-tree/host>`, namespaces like `<ns/path>`. 149 - <LinkButton href="/guides/angle-brackets" variant="minimal" icon="right-arrow">Learn More</LinkButton> 150 - </Card> 151 - <Card title="Namespaces" icon="seti:folder-dist"> 152 - Define local namespaces, consume remote ones, nested provides, expose as flake output, merge multiple sources. 153 - <LinkButton href="/guides/namespaces" variant="minimal" icon="right-arrow">Learn More</LinkButton> 154 - </Card> 155 - <Card title="Cross-Provider Contexts" icon="random"> 156 - `den.ctx.*.into.*` cross-provider derivation — transform config between context types, per-value providers. 157 - <LinkButton href="/reference/ctx" variant="minimal" icon="right-arrow">Learn More</LinkButton> 158 - </Card> 159 - <Card title="Host Propagation" icon="list-format"> 160 - Full propagation: owned, static, lax, exact, atLeast contexts with default includes, hm-host, and hm-user stages. 161 - <LinkButton href="/explanation/context-pipeline" variant="minimal" icon="right-arrow">Learn More</LinkButton> 162 - </Card> 163 - <Card title="Default Includes" icon="seti:default"> 164 - `den.default.includes` — global defaults applied to all hosts/users, dynamic class resolution, hostname from context. 165 - <LinkButton href="/explanation/context-pipeline" variant="minimal" icon="right-arrow">Learn More</LinkButton> 166 - </Card> 167 - <Card title="Schema Base Modules" icon="document"> 168 - `den.base.conf`, `den.base.host`, `den.base.user`, `den.base.home` — extend entity schemas with custom modules. 169 - <LinkButton href="/reference/schema" variant="minimal" icon="right-arrow">Learn More</LinkButton> 170 - </Card> 171 - <Card title="Custom Instantiation" icon="setting"> 172 - Override `instantiate` for standalone HM with `osConfig` special arg, or custom deployment targets. 173 - <LinkButton href="/guides/declare-hosts" variant="minimal" icon="right-arrow">Learn More</LinkButton> 174 - </Card> 175 - <Card title="Conditional Config" icon="approve-check"> 176 - Conditional NixOS imports based on host/user attributes. Per-user HM config. Static aspects in defaults. 177 - <LinkButton href="/guides/configure-aspects" variant="minimal" icon="right-arrow">Learn More</LinkButton> 178 - </Card> 179 - <Card title="External Providers" icon="external"> 180 - Consume aspects from external flakes. Provider flake integration, deep nested namespace aspects. 181 - <LinkButton href="/guides/namespaces" variant="minimal" icon="right-arrow">Learn More</LinkButton> 182 - </Card> 183 - </CardGrid> 37 + ## Templates 184 38 185 - ## Getting Started 39 + Step-by-step walkthroughs for each starter template. 186 40 187 41 <CardGrid> 188 - <Card title="Minimal Template" icon="seti:mint"> 189 - Smallest possible setup — one host, one user, no extra deps. Single `flake.nix` + `modules/den.nix`. 190 - <LinkButton href="/tutorials/minimal" variant="minimal" icon="right-arrow">Learn More</LinkButton> 191 - </Card> 192 - <Card title="Default Template" icon="star"> 193 - Recommended starting point with flake-parts, Home-Manager, and VM testing. Full file walkthrough. 194 - <LinkButton href="/tutorials/default" variant="minimal" icon="right-arrow">Learn More</LinkButton> 195 - </Card> 196 - <Card title="Example Template" icon="seti:code-climate"> 197 - Advanced: cross-platform hosts, namespaces, angle brackets, bidirectional providers, custom routes. 198 - <LinkButton href="/tutorials/example" variant="minimal" icon="right-arrow">Learn More</LinkButton> 199 - </Card> 200 - <Card title="No-Flake Template" icon="seti:lock"> 201 - Using Den with stable Nix via npins. No flakes required. Works with nix-maid. 202 - <LinkButton href="/tutorials/noflake" variant="minimal" icon="right-arrow">Learn More</LinkButton> 203 - </Card> 204 - <Card title="Bogus / Bug Report" icon="warning"> 205 - Bug reproduction template with nix-unit. `denTest` helper and available test fixtures. 206 - <LinkButton href="/tutorials/bogus" variant="minimal" icon="right-arrow">Learn More</LinkButton> 207 - </Card> 208 - <Card title="Templates Overview" icon="open-book"> 209 - Comparison table of all 6 templates, quick start commands, project structure. 210 - <LinkButton href="/tutorials/overview" variant="minimal" icon="right-arrow">Learn More</LinkButton> 211 - </Card> 42 + <LinkCard title="Templates Overview" href="/tutorials/overview/" description="Comparison of all available templates." /> 43 + <LinkCard title="Default" href="/tutorials/default/" description="Recommended: flake-parts + home-manager." /> 44 + <LinkCard title="Minimal" href="/tutorials/minimal/" description="Minimal flake, no flake-parts." /> 45 + <LinkCard title="Example" href="/tutorials/example/" description="Cross-platform NixOS + Darwin." /> 46 + <LinkCard title="No-Flake" href="/tutorials/noflake/" description="npins + lib.evalModules." /> 47 + <LinkCard title="CI Tests" href="/tutorials/ci/" description="Feature tests as executable code examples." /> 48 + <LinkCard title="Bug Reproduction" href="/tutorials/bogus/" description="Isolated template for bug reports." /> 212 49 </CardGrid> 213 50 214 - ## Advanced & Operations 51 + ## Reference 52 + 53 + API documentation for every Den option. 215 54 216 55 <CardGrid> 217 - <Card title="Migration Guide" icon="right-caret"> 218 - Incremental adoption from flake-parts, import existing modules, mix Den with existing `nixosSystem`, recommended path. 219 - <LinkButton href="/guides/migrate" variant="minimal" icon="right-arrow">Learn More</LinkButton> 220 - </Card> 221 - <Card title="Debugging" icon="warning"> 222 - `builtins.trace`, `builtins.break`, REPL inspection, manually resolving aspects, common issues. 223 - <LinkButton href="/guides/debug" variant="minimal" icon="right-arrow">Learn More</LinkButton> 224 - </Card> 225 - <Card title="CI Test Suite" icon="seti:test"> 226 - 40+ test files covering every feature. Run tests, write new ones, understand test harness (`denTest`, `evalDen`). 227 - <LinkButton href="/tutorials/ci" variant="minimal" icon="right-arrow">Learn More</LinkButton> 228 - </Card> 56 + <LinkCard title="den.ctx" href="/reference/ctx/" description="Context types, into transformations, provides." /> 57 + <LinkCard title="den.lib" href="/reference/lib/" description="parametric, canTake, take, statics, owned, __findFile." /> 58 + <LinkCard title="den.aspects" href="/reference/aspects/" description="Aspect type, resolution, class configs." /> 59 + <LinkCard title="den.base" href="/reference/schema/" description="Host, user, home schema options." /> 60 + <LinkCard title="den.provides" href="/reference/batteries/" description="Built-in batteries reference." /> 61 + <LinkCard title="flake.*" href="/reference/output/" description="Output generation and instantiation." /> 229 62 </CardGrid> 230 63 231 64 ## Community & Project 232 65 233 66 <CardGrid> 234 - <Card title="Motivation & History" icon="heart"> 235 - Why Den exists — from vic/vix through blueprint, snowfall, and flake-parts to dendritic design. 236 - <LinkButton href="/motivation" variant="minimal" icon="right-arrow">Learn More</LinkButton> 237 - </Card> 238 - <Card title="Community" icon="github"> 239 - GitHub Discussions, Zulip Chat, Matrix channel, real-world examples, ecosystem projects. 240 - <LinkButton href="/community" variant="minimal" icon="right-arrow">Learn More</LinkButton> 241 - </Card> 242 - <Card title="Contributing" icon="add-document"> 243 - Report bugs, contribute PRs, run tests, format code. 244 - <LinkButton href="/contributing" variant="minimal" icon="right-arrow">Learn More</LinkButton> 245 - </Card> 246 - <Card title="Sponsor" icon="heart"> 247 - Support Den development via GitHub Sponsors. 248 - <LinkButton href="/sponsor" variant="minimal" icon="right-arrow">Learn More</LinkButton> 249 - </Card> 67 + <LinkCard title="Community" href="/community/" description="Discussions, real-world examples, ecosystem." /> 68 + <LinkCard title="Contributing" href="/contributing/" description="Tests, CI, code style." /> 69 + <LinkCard title="Sponsor" href="/sponsor/" description="Support Den development." /> 250 70 </CardGrid>
+73 -46
docs/src/content/docs/reference/aspects.mdx
··· 1 1 --- 2 - title: den.aspects Reference 3 - description: Aspect structure, resolution, and configuration. 2 + title: den.aspects 3 + description: Aspect type system, resolution, class configuration, and includes dispatch. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`modules/aspects.nix`](https://github.com/vic/den/blob/main/modules/aspects.nix) · [`modules/aspects/definition.nix`](https://github.com/vic/den/blob/main/modules/aspects/definition.nix) · [`modules/aspects/provides.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides.nix) · Tests: [`user-host-bidirectional-config.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/user-host-bidirectional-config.nix) · [`top-level-parametric.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/top-level-parametric.nix)</Aside> 8 + <Aside title="Source" icon="github"> 9 + [`modules/aspects.nix`](https://github.com/vic/den/blob/main/modules/aspects.nix) -- 10 + [`modules/aspects/definition.nix`](https://github.com/vic/den/blob/main/modules/aspects/definition.nix) -- 11 + [`modules/aspects/provides.nix`](https://github.com/vic/den/blob/main/modules/aspects/provides.nix) 12 + </Aside> 9 13 10 - ## Aspect Attributes 14 + ## `den.aspects` 11 15 12 - Every aspect is an attribute set with these recognized keys: 16 + Type: `aspectsType` (from `flake-aspects`) 13 17 14 - | Attribute | Type | Description | 15 - |-----------|------|-------------| 16 - | `description` | str | Human-readable description | 17 - | `__functor` | function | Context-aware behavior | 18 - | `includes` | list | Dependencies on other aspects or functions | 19 - | `provides` | attrset | Nested sub-aspects | 20 - | `_` | alias | Shorthand for `provides` | 21 - | `<class>` | module | Any Nix class, like the following | 22 - | `nixos` | module | NixOS configuration module | 23 - | `darwin` | module | nix-darwin configuration module | 24 - | `homeManager` | module | Home-Manager configuration module | 18 + An attribute set of aspects. Each aspect key names an aspect; its value is 19 + an aspect set containing per-class config and an `includes` list: 25 20 26 - ## Automatic Aspect Creation 21 + ```nix 22 + den.aspects = { 23 + dns = { 24 + nixos.services.resolved.enable = true; 25 + darwin.networking.dns = [ "1.1.1.1" ]; 26 + includes = [ ./dns ]; 27 + }; 28 + }; 29 + ``` 27 30 28 - Den creates an aspect for each host, user, and home you declare: 31 + ### Auto-generation 32 + 33 + Den auto-generates `den.aspects` entries from `den.hosts`, `den.homes`, 34 + and `den.users`. For every declared host/home/user, an aspect is created 35 + with the appropriate class configurations. You do not need to declare 36 + `den.aspects` manually unless adding shared aspects. 37 + 38 + ## `den.ful` 39 + 40 + Type: `attrsOf aspectsType` 41 + 42 + Namespaced aspect collections. Each key is a namespace name, each value 43 + is a full `aspectsType`. Populated by `den.namespace` or by merging 44 + upstream `denful` flake outputs. 29 45 30 46 ```nix 31 - den.hosts.x86_64-linux.igloo.users.tux = { }; 47 + den.ful.myns = { 48 + some-aspect = { nixos.services.foo.enable = true; }; 49 + }; 50 + ``` 51 + 52 + ## `flake.denful` 32 53 33 - # will create: 54 + Type: `attrsOf raw` 34 55 35 - den.aspects.igloo = parametric { }; 36 - den.aspects.tux = parametric { }; 37 - ``` 56 + Raw flake output for publishing namespaces. Set automatically by 57 + `den.namespace`; consumed by downstream flakes that import your aspects. 38 58 39 - ## Aspect Resolution 59 + ## Aspect structure 40 60 41 - To extract a class module from an aspect use the flake-aspects API: 61 + An aspect is an attribute set with: 42 62 43 - ```nix 44 - module = aspect.resolve { class = "nixos"; aspect-chain = []; }; 45 - ``` 63 + | Key | Purpose | 64 + |-----|---------| 65 + | `<class>` | Config merged into hosts/homes of that class | 66 + | `includes` | List of modules or functions dispatched by context | 67 + | `__functor` | Auto-generated by `parametric`; drives dispatch | 46 68 47 - Resolution collects the specified class from the aspect and all 48 - transitive includes into a single merged Nix module. 69 + ### Static vs parametric includes 49 70 50 - ## den.aspects (namespace) 71 + Functions in `includes` receiving `{ class, aspect-chain }` are **static** -- 72 + evaluated once during aspect resolution. Functions receiving context 73 + arguments (`{ host }`, `{ user }`, etc.) are **parametric** -- evaluated 74 + per context during `ctxApply`. 51 75 52 - This namespace is where Den creates aspects for your host/user/home. 76 + ## `den.provides` 53 77 54 - You can also use this namespace to create your own aspects. 55 - Aspects in this namespace are not shared outside the flake, 56 - for that use `den.namespace`. 78 + Type: freeform `attrsOf providerType` (aliased as `den._`) 57 79 58 - Contributions from any module are merged: 80 + Batteries-included reusable aspects. Each provider is a `providerType` 81 + from `flake-aspects`. See [Batteries Reference](/reference/batteries/). 59 82 60 83 ```nix 61 - # file1.nix 62 - den.aspects.igloo.nixos.networking.hostName = "igloo"; 63 - 64 - # file2.nix 65 - den.aspects.igloo.homeManager.programs.vim.enable = true; 84 + den._ = { 85 + my-battery = { 86 + nixos.services.something.enable = true; 87 + includes = [ ./my-module.nix ]; 88 + }; 89 + }; 66 90 ``` 67 91 68 - ## den.provides (namespace) 92 + ## Class resolution 69 93 70 - Den batteries-included re-usable aspects. 71 - 72 - Access via `den._.name` or `den.provides.name`. 94 + When aspects are resolved for a host, Den: 73 95 74 - See [Batteries Reference](/reference/batteries/) for all built-in aspects. 96 + 1. Collects all aspects referenced by the host 97 + 2. Extracts the class-specific config (e.g., `nixos` for NixOS hosts) 98 + 3. Evaluates static includes with `{ class, aspect-chain }` 99 + 4. Builds context pairs from `den.ctx` 100 + 5. Applies parametric includes via `ctxApply` 101 + 6. Merges everything into the host's `evalModules` call
+70 -149
docs/src/content/docs/reference/batteries.mdx
··· 1 1 --- 2 - title: Batteries Reference 3 - description: All built-in opt-in aspects shipped with Den. 2 + title: Batteries 3 + description: All den.provides batteries -- reusable aspect providers shipped with Den. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`modules/aspects/provides/`](https://github.com/vic/den/tree/main/modules/aspects/provides) · Tests: [`batteries/`](https://github.com/vic/den/tree/main/templates/ci/modules/features/batteries)</Aside> 8 + <Aside title="Source" icon="github"> 9 + [`modules/aspects/provides/`](https://github.com/vic/den/blob/main/modules/aspects/provides/) 10 + </Aside> 9 11 10 - ## Overview 12 + Den ships reusable aspect providers under `den.provides` (aliased `den._`). 13 + Each battery contributes NixOS/Darwin/home-manager modules to hosts and 14 + users that include them via `den.ctx`. 11 15 12 - Batteries are pre-built aspects at `den.provides` (accessed via `den._.<name>`). 13 - All are opt-in — include them explicitly where needed. 16 + ## System batteries 14 17 15 - ## define-user 18 + ### `den._.define-user` 16 19 17 - Defines a user at OS and Home-Manager levels. 18 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/define-user.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/define-user.nix)) 20 + Creates OS-level user accounts (`users.users.<name>`) with `isNormalUser`, 21 + `home`, and group. Sets `home-manager.users.<name>` when home-manager 22 + integration is active. 19 23 20 - ```nix 21 - den.default.includes = [ den._.define-user ]; 22 - ``` 24 + ### `den._.os-user` 23 25 24 - **Sets:** 25 - - `users.users.<name>.{name, home, isNormalUser}` (NixOS) 26 - - `users.users.<name>.{name, home}` (Darwin) 27 - - `home.{username, homeDirectory}` (Home-Manager) 26 + Assigns a user into the host by setting group membership and ensuring 27 + the user account exists at the OS level. 28 28 29 - **Contexts:** `{ host, user }`, `{ home }` 29 + ### `den._.primary-user` 30 30 31 - ## primary-user 31 + Marks a user as the primary user of a host. Sets `nix.settings.trusted-users` 32 + and can configure auto-login or default shell based on other batteries. 32 33 33 - Makes a user an administrator. 34 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/primary-user.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/primary-user.nix)) 34 + ### `den._.user-shell` 35 35 36 - ```nix 37 - den.aspects.vic.includes = [ den._.primary-user ]; 38 - ``` 36 + Sets the user's login shell. Reads `user.shell` and configures both 37 + `users.users.<name>.shell` and the home-manager `programs.<shell>.enable`. 39 38 40 - **Sets:** 41 - - NixOS: `users.users.<name>.extraGroups = [ "wheel" "networkmanager" ]` 42 - - Darwin: `system.primaryUser = <name>` 43 - - WSL: `wsl.defaultUser = <name>` (if host has `wsl` attribute) 39 + ### `den._.tty-autologin` 44 40 45 - **Context:** `{ host, user }` 41 + Configures TTY auto-login for the primary user via 42 + `services.getty.autologinUser`. 46 43 47 - ## user-shell 44 + ### `den._.wsl` 48 45 49 - Sets default shell at OS and HM levels. 50 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/user-shell.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/user-shell.nix)) 46 + WSL-specific configuration. Enables `wsl.enable`, sets the default user, 47 + and adjusts networking for WSL2 environments. 51 48 52 - ```nix 53 - den.aspects.vic.includes = [ (den._.user-shell "fish") ]; 54 - ``` 49 + ### `den._.forward` 55 50 56 - **Sets:** 57 - - `programs.<shell>.enable = true` (NixOS/Darwin) 58 - - `users.users.<name>.shell = pkgs.<shell>` (NixOS/Darwin) 59 - - `programs.<shell>.enable = true` (Home-Manager) 51 + Forwards aspect configuration from one aspect to another. Used to chain 52 + aspects without duplicating declarations. 60 53 61 - **Contexts:** `{ host, user }`, `{ home }` 54 + ### `den._.import-tree` 62 55 63 - ## unfree 56 + Uses `inputs.import-tree` to recursively import a directory tree as 57 + aspect modules. Enables file-system-driven aspect organization. 64 58 65 - Enables unfree packages by name. 66 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/unfree/unfree-predicate-builder.nix) · [predicate](https://github.com/vic/den/blob/main/modules/aspects/provides/unfree/unfree.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/unfree.nix)) 59 + ## Flake-parts batteries 67 60 68 - ```nix 69 - den.aspects.laptop.includes = [ (den._.unfree [ "discord" ]) ]; 70 - ``` 61 + ### `den._.inputs'` 71 62 72 - **Sets:** `unfree.packages` option + `nixpkgs.config.allowUnfreePredicate` 63 + Exposes `inputs'` (system-qualified inputs) into aspect modules. 64 + Source: `provides/flake-parts/inputs.nix` 73 65 74 - **Contexts:** All (host, user, home) — works for any class. 66 + ### `den._.self'` 75 67 76 - :::note[useGlobalPkgs interaction] 77 - When Home-Manager's `useGlobalPkgs` is `true`, the unfree module 78 - skips setting `nixpkgs.config` on the HM class to avoid conflicts. 79 - Set unfree packages on the **host** aspect instead. 80 - ::: 68 + Exposes `self'` (system-qualified self outputs) into aspect modules. 69 + Source: `provides/flake-parts/self.nix` 81 70 82 - ## tty-autologin 71 + ## Home-manager batteries 83 72 84 - Automatic tty login. 85 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/tty-autologin.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/tty-autologin.nix)) 73 + ### `den._.hm-integration` 86 74 87 - ```nix 88 - den.aspects.laptop.includes = [ (den._.tty-autologin "root") ]; 89 - ``` 75 + Integrates home-manager into NixOS/Darwin hosts. Imports the appropriate 76 + home-manager module (`nixos` or `darwin`), enables 77 + `home-manager.useGlobalPkgs` and `useUserPackages`. 90 78 91 - **Sets:** `systemd.services."getty@tty1"` with autologin. 79 + ### `den._.hm-os` 92 80 93 - **Class:** NixOS only. 81 + Merges home-manager configuration into the host OS module system. Bridges 82 + `home-manager.users.<name>` with the user's aspect. 94 83 95 - ## import-tree 84 + ## Hjem batteries 96 85 97 - Auto-imports non-dendritic Nix files by class directory. 98 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/import-tree.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/import-tree.nix)) 86 + ### `den._.hjem-integration` 99 87 100 - ```nix 101 - den.aspects.laptop.includes = [ (den._.import-tree ./path) ]; 102 - ``` 88 + Integrates [hjem](https://github.com/feel-co/hjem) as an alternative to 89 + home-manager. Imports hjem's NixOS module. 103 90 104 - Looks for `./path/_nixos/`, `./path/_darwin/`, `./path/_homeManager/`. 91 + ### `den._.hjem-os` 105 92 106 - **Helpers:** 93 + Merges hjem user configuration into the host. Sets `hjem.users.<name>` 94 + from the user's aspect. 107 95 108 - ```nix 109 - den._.import-tree._.host ./hosts # per host: ./hosts/<name>/_<class> 110 - den._.import-tree._.user ./users # per user: ./users/<name>/_<class> 111 - den._.import-tree._.home ./homes # per home: ./homes/<name>/_<class> 112 - ``` 96 + ## Maid batteries 113 97 114 - **Requires:** `inputs.import-tree` 98 + ### `den._.maid-integration` 115 99 116 - ## inputs' (flake-parts) 100 + Integrates [maid](https://github.com/maid-nix/maid) as an alternative 101 + home management system. 117 102 118 - Provides per-system `inputs'` as a module argument. 119 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/flake-parts/inputs.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/flake-parts.nix)) 103 + ### `den._.maid-os` 120 104 121 - ```nix 122 - den.default.includes = [ den._.inputs' ]; 123 - ``` 105 + Merges maid configuration into the host OS for each user. 124 106 125 - **Requires:** flake-parts with `withSystem`. 107 + ## Unfree batteries 126 108 127 - ## self' (flake-parts) 109 + ### `den._.unfree` 128 110 129 - Provides per-system `self'` as a module argument. 130 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/flake-parts/self.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/batteries/flake-parts.nix)) 111 + Allows unfree packages globally via 112 + `nixpkgs.config.allowUnfree = true`. 131 113 132 - ```nix 133 - den.default.includes = [ den._.self' ]; 134 - ``` 114 + ### `den._.unfree-predicate` 135 115 136 - **Requires:** flake-parts with `withSystem`. 137 - 138 - ## forward 139 - 140 - Creates custom Nix classes by forwarding configs between classes. 141 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/forward.nix) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/forward-from-custom-class.nix) · [usage guide](/guides/custom-classes/)) 142 - 143 - ```nix 144 - den._.forward { 145 - each = lib.singleton class; 146 - fromClass = _: "source"; 147 - intoClass = _: "nixos"; 148 - intoPath = _: [ "target" "path" ]; 149 - fromAspect = _: sourceAspect; 150 - guard = { options, ... }: options ? target; # optional 151 - } 152 - ``` 153 - 154 - Returns an aspect. Supports optional `guard` for conditional forwarding. 155 - Used internally for Home-Manager, nix-maid, and hjem integrations. 156 - 157 - ## home-manager 158 - 159 - HM integration is handled by 160 - `den.ctx.hm-host` and `den.ctx.hm-user` context types, which are 161 - activated automatically when hosts have HM users. 162 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/home-manager/) · [tests](https://github.com/vic/den/blob/main/templates/ci/modules/features/home-manager/)) 163 - 164 - All `homeManager` class settings are forwarded to os-level `home-manager.users.<userName>`. 165 - 166 - ## maid 167 - 168 - nix-maid integration is handled by `den.ctx.maid-host` and `den.ctx.maid-user`, 169 - activated when hosts have users with `"maid"` in their `classes`. 170 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/maid/)) 171 - 172 - Forwards `maid` class settings to os-level `users.users.<userName>.maid`. 173 - Requires `inputs.nix-maid` or a custom `host.maid-module`. 174 - 175 - ## hjem 176 - 177 - hjem integration is handled by `den.ctx.hjem-host` and `den.ctx.hjem-user`, 178 - activated when hosts have users with `"hjem"` in their `classes`. 179 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/hjem/)) 180 - 181 - Forwards `hjem` class settings to os-level `hjem.users.<userName>`. 182 - Requires `inputs.hjem` or a custom `host.hjem-module`. 183 - 184 - ## user 185 - 186 - The `user` class is automatically handled by Den and forwards to os-level `users.users.<userName>` 187 - ([source](https://github.com/vic/den/blob/main/modules/aspects/provides/os-user.nix)) 188 - 189 - You can write: 190 - 191 - ```nix 192 - den.aspects.tux.user.description = "bird"; 193 - 194 - # same as setting at host: 195 - den.aspects.igloo.nixos.users.users.tux.description = "bird"; 196 - ``` 197 - 116 + Allows unfree packages selectively using a predicate builder. Accepts 117 + a list of package names and builds 118 + `nixpkgs.config.allowUnfreePredicate`.
+86 -153
docs/src/content/docs/reference/ctx.mdx
··· 1 1 --- 2 - title: den.ctx Reference 3 - description: Context type definitions and their built-in implementations. 2 + title: den.ctx 3 + description: Context types, into transformations, and provides. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) · [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) · [`modules/aspects/defaults.nix`](https://github.com/vic/den/blob/main/modules/aspects/defaults.nix) · Tests: [`context/`](https://github.com/vic/den/tree/main/templates/ci/modules/features/context) · [`host-propagation.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/context/host-propagation.nix)</Aside> 9 - 10 - ## Context Type Schema 11 - 12 - Each `den.ctx.<name>` is an [aspect submodule](/explanation/aspects/) extended 13 - with context transformations. It inherits all options from `flake-aspects`'s 14 - `aspect`-type and adds `into`, and a couple of conventions: 15 - 16 - | Option | Type | Description | 17 - |--------|------|-------------| 18 - | `description` | `str` | Human-readable description | 19 - | `provides.${name}` | `ctx → aspect` | Self-named provider: locates the aspect for this context | 20 - | `provides.${target}` | `ctx → aspect` | Cross-provider: source's contribution to target contexts | 21 - | `into` | `attrset of (ctx → list)` | Transformations to other contexts | 22 - | `includes` | `list of aspect` | Parametric aspects activated for this context | 8 + <Aside title="Source" icon="github"> 9 + [`modules/context/types.nix`](https://github.com/vic/den/blob/main/modules/context/types.nix) -- 10 + [`modules/context/os.nix`](https://github.com/vic/den/blob/main/modules/context/os.nix) 11 + </Aside> 23 12 24 - Context types are also functors — callable as functions: 13 + ## `den.ctx` 25 14 26 - ```nix 27 - aspect = den.ctx.host { host = den.hosts.x86_64-linux.igloo; }; 28 - ``` 15 + Type: `lazyAttrsOf ctxType` 29 16 30 - ## ctxApply (internal) 17 + A map of context type names to context type definitions. Each context type 18 + defines how data flows through the evaluation pipeline. 31 19 32 - When a context type is applied, `ctxApply` walks the full `into` graph 33 - and deduplicates includes using a seen-set: 20 + ## Context Type Options 34 21 35 - ```nix 36 - ctxApply = ctxName: _self: ctx: 37 - let 38 - pairs = collectPairs null den.ctx.${ctxName} ctx; 39 - in 40 - { includes = dedupIncludes pairs; }; 41 - ``` 22 + Each `den.ctx.<name>` has: 42 23 43 - **collectPairs** recursively walks `into` transformations, producing 44 - `(source, ctxDef, ctx)` triples for every reachable context type. 45 - The `source` tracks which context type produced the transformation. 24 + ### `description` 46 25 47 - **dedupIncludes** processes triples with a seen-set keyed by context name: 26 + Type: `str` 48 27 49 - - **First visit**: `fixedTo ctx (cleanCtx ctxDef)` — dispatches owned, static, and parametric includes — plus self-provider and cross-provider 50 - - **Subsequent visits**: `atLeast (cleanCtx ctxDef) ctx` — dispatches only parametric includes — plus self-provider and cross-provider 28 + Human-readable description of this context type. 51 29 52 - For each triple, three providers are called: 53 - 1. **Self-provider** `ctxDef.provides.${ctxDef.name}` — the target's own aspect lookup 54 - 2. **Cross-provider** `source.provides.${ctxDef.name}` — the source's contribution to this target (if defined) 30 + ### `_` (alias: `provides`) 55 31 56 - ## Transformation Types 32 + Type: `attrsOf providerType` 57 33 58 - All `into` transformations have the type `source → [ target ]`: 34 + Maps context names to provider functions. When this context is processed, 35 + each provider is called with the current context data and returns aspect fragments. 59 36 60 - - **Fan-out**: `{ host }: map (u: { inherit host user; }) users` — one host produces N contexts 61 - - **Conditional**: `{ host }: lib.optional (test host) { inherit host; }` — zero or one 62 - - **Pass-through**: `lib.singleton` — forward the same data as-is 37 + ```nix 38 + den.ctx.host._.host = { host }: 39 + parametric.fixedTo { inherit host; } den.aspects.${host.aspect}; 40 + ``` 63 41 64 - An empty list means the target context is not created. This enables 65 - conditional activation like HM detection without any explicit `if` logic 66 - in the pipeline. 42 + ### `into` 67 43 68 - ## Contexts as Cutting-Points 44 + Type: `lazyAttrsOf (functionTo (listOf raw))` 69 45 70 - Context types have their own owned configs and `includes`, making them 71 - aspect-like cutting-points in the pipeline: 46 + Maps other context type names to transformation functions. Each function 47 + takes the current context data and returns a list of new context values. 72 48 73 49 ```nix 74 - den.ctx.hm-host.nixos.home-manager.useGlobalPkgs = true; 75 - den.ctx.hm-host.includes = [ 76 - ({ host, ... }: { nixos.home-manager.backupFileExtension = "bak"; }) 77 - ]; 50 + den.ctx.host.into.user = { host }: 51 + map (user: { inherit host user; }) (lib.attrValues host.users); 78 52 ``` 79 53 54 + ### `includes` 80 55 81 - ## Extending Context Flow 82 - 83 - Add transformations to existing context types from any module: 56 + Aspect includes attached to this context type. Used by batteries to inject 57 + behavior at specific pipeline stages. 84 58 85 - ```nix 86 - den.ctx.hm-host.into.foo = { host }: [ { foo = host.name; } ]; 87 - den.ctx.foo._.foo = { foo }: { funny.names = [ foo ]; }; 88 - ``` 59 + ### `modules` 89 60 90 - The module system merges these definitions. You can also override a 91 - host's `mainModule` to use a completely custom context flow. 61 + Additional modules merged into the resolved output. 92 62 93 63 ## Built-in Context Types 94 64 95 - ### den.ctx.host 65 + ### `den.ctx.host` 96 66 97 - | Field | Value | 98 - |-------|-------| 99 - | `description` | OS | 100 - | `provides.host` | `{ host }:` fixedTo host aspect | 101 - | `provides.user` | `{ host, user }:` host's contribution to user contexts (cross-provider) | 102 - | `into.default` | `lib.singleton` (pass-through) | 103 - | `into.user` | Enumerate `host.users` | 104 - | `into.hm-host` | Detect HM support (from `hm-os.nix`) | 105 - | `into.maid-host` | Detect nix-maid support (from `maid-os.nix`) | 106 - | `into.hjem-host` | Detect hjem support (from `hjem-os.nix`) | 67 + Context data: `{ host }` 107 68 108 - ### den.ctx.user 69 + Produced for each `den.hosts.<system>.<name>` entry. 109 70 110 - | Field | Value | 111 - |-------|-------| 112 - | `description` | OS user | 113 - | `provides.user` | `{ host, user }:` fixedTo user aspect | 114 - | `into.default` | `lib.singleton` (pass-through) | 71 + Providers: 72 + - `_.host` -- `fixedTo { host }` on the host's aspect. 73 + - `_.user` -- `atLeast` on the host's aspect with `{ host, user }`. 115 74 116 - ### den.ctx.default 75 + Transitions: 76 + - `into.default` -- identity (for default aspect). 77 + - `into.user` -- one `{ host, user }` per `host.users` entry. 78 + - `into.hm-host` -- (from `hm-os.nix`) if HM enabled and has HM users. 79 + - `into.wsl-host` -- (from `wsl.nix`) if WSL enabled on NixOS host. 80 + - `into.hjem-host` -- (from `hjem-os.nix`) if hjem enabled. 81 + - `into.maid-host` -- (from `maid-os.nix`) if nix-maid enabled. 117 82 118 - | Field | Value | 119 - |-------|-------| 120 - | `provides.default` | `_: { }` (empty — just a dispatcher) | 83 + ### `den.ctx.user` 121 84 122 - Aliased as `den.default` via `lib.mkAliasOptionModule`. 123 - Writing `den.default.foo` is identical to `den.ctx.default.foo`. 85 + Context data: `{ host, user }` 124 86 125 - Host/user/home context types transforms into `default`, so `den.default.includes` 126 - functions run at each of their pipeline stages. However, owned and static configs 127 - are **automatically deduplicated** — only the first visit gets them. 128 - Parametric functions still run at every stage; use `take.exactly` to 129 - restrict matching if needed. 87 + Providers: 88 + - `_.user` -- `fixedTo { host, user }` on the user's aspect. 130 89 131 - ### den.ctx.home 90 + Transitions: 91 + - `into.default` -- identity. 132 92 133 - | Field | Value | 134 - |-------|-------| 135 - | `description` | Standalone Home-Manager config | 136 - | `provides.home` | `{ home }:` fixedTo home aspect | 137 - | `into.default` | `lib.singleton` | 93 + ### `den.ctx.home` 138 94 139 - ### den.ctx.hm-host 95 + Context data: `{ home }` 140 96 141 - | Field | Value | 142 - |-------|-------| 143 - | `description` | Host with HM-supported OS and HM users | 144 - | `provides.hm-host` | `{ host }:` imports HM NixOS/Darwin module | 145 - | `into.hm-user` | Enumerate HM-class users | 97 + Produced for each `den.homes.<system>.<name>` entry. 146 98 147 - **Detection criteria** (all must be true): 148 - 1. Host class is `nixos` or `darwin` 149 - 2. At least one user has `"homeManager"` in their `classes` 150 - 3. `inputs.home-manager` exists, or host has a custom `hm-module` attribute 99 + Providers: 100 + - `_.home` -- `fixedTo { home }` on the home's aspect. 151 101 152 - If detection fails, the HM pipeline is skipped entirely. 102 + ### `den.ctx.hm-host` 153 103 154 - ### den.ctx.hm-user 104 + Context data: `{ host }` 155 105 156 - | Field | Value | 157 - |-------|-------| 158 - | `description` | Internal — forwards HM class to host | 159 - | `provides.hm-user` | `{ host, user }:` forward homeManager into host | 106 + Providers: 107 + - `provides.hm-host` -- imports HM OS module. 160 108 161 - ### den.ctx.hm-internal-user (internal) 109 + Transitions: 110 + - `into.hm-user` -- per HM-class user. 162 111 163 - An internal context type used by `hm-user`. It combines: 164 - - The user context (`den.ctx.user { host, user }`) 165 - - Owned configs from the host aspect 166 - - Static includes from the host aspect 167 - - Parametric includes from the host aspect matching `{ host, user }` 112 + ### `den.ctx.hm-user` 168 113 169 - This ensures that when the `homeManager` class is forwarded into 170 - `home-manager.users.<name>`, it receives contributions from both 171 - the user's aspect and the host's aspect. 114 + Context data: `{ host, user }` 172 115 173 - ### den.ctx.maid-host 116 + Providers: 117 + - `_.hm-user` -- forwards `homeManager` class to `home-manager.users.<userName>`. 174 118 175 - | Field | Value | 176 - |-------|-------| 177 - | `description` | Host with nix-maid users | 178 - | `provides.maid-host` | `{ host }:` imports nix-maid NixOS module | 179 - | `into.maid-user` | Enumerate maid-class users | 119 + ### `den.ctx.wsl-host` 180 120 181 - **Detection criteria** (all must be true): 182 - 1. Host class is `nixos` or `darwin` 183 - 2. At least one user has `"maid"` in their `classes` 184 - 3. `inputs.nix-maid` exists, or host has a custom `maid-module` attribute 121 + Context data: `{ host }` 185 122 186 - ### den.ctx.maid-user 123 + Providers: 124 + - `provides.wsl-host` -- imports WSL module, creates `wsl` class forward. 187 125 188 - | Field | Value | 189 - |-------|-------| 190 - | `provides.maid-user` | `{ host, user }:` forward maid class to `users.users.<name>.maid` | 126 + ## Custom Context Types 191 127 192 - ### den.ctx.hjem-host 128 + Define new contexts to extend the pipeline: 193 129 194 - | Field | Value | 195 - |-------|-------| 196 - | `description` | Host with hjem users | 197 - | `provides.hjem-host` | `{ host }:` imports hjem NixOS module | 198 - | `into.hjem-user` | Enumerate hjem-class users | 130 + ```nix 131 + { 132 + den.ctx.gpu = { 133 + description = "GPU-enabled host"; 134 + _.gpu = { host }: { 135 + nixos.hardware.nvidia.enable = true; 136 + }; 137 + }; 199 138 200 - **Detection criteria** (all must be true): 201 - 1. Host class is `nixos` or `darwin` 202 - 2. At least one user has `"hjem"` in their `classes` 203 - 3. `inputs.hjem` exists, or host has a custom `hjem-module` attribute 204 - 205 - ### den.ctx.hjem-user 206 - 207 - | Field | Value | 208 - |-------|-------| 209 - | `provides.hjem-user` | `{ host, user }:` forward hjem class to `hjem.users.<name>` | 139 + den.ctx.host.into.gpu = { host }: 140 + lib.optional (host ? gpu) { inherit host; }; 141 + } 142 + ```
+62 -93
docs/src/content/docs/reference/lib.mdx
··· 1 1 --- 2 - title: den.lib Reference 3 - description: Library functions for parametric dispatch and context handling. 2 + title: den.lib 3 + description: Library functions for parametric dispatch, argument introspection, and aspect manipulation. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) · [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) · [`nix/den-brackets.nix`](https://github.com/vic/den/blob/main/nix/den-brackets.nix) · Tests: [`parametric.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/parametric.nix) · [`angle-brackets.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/angle-brackets.nix)</Aside> 8 + <Aside title="Source" icon="github"> 9 + [`nix/lib.nix`](https://github.com/vic/den/blob/main/nix/lib.nix) -- 10 + [`nix/fn-can-take.nix`](https://github.com/vic/den/blob/main/nix/fn-can-take.nix) 11 + </Aside> 9 12 10 - ## den.lib.parametric 13 + ## `den.lib.parametric` 11 14 12 - Creates a parametric aspect. Alias for `(parametric.withOwn parametric.atLeast)`. 15 + Wraps an aspect with a `__functor` that filters `includes` by argument compatibility. 13 16 14 17 ```nix 15 - den.lib.parametric { nixos.x = 1; includes = [ f ]; } 18 + den.lib.parametric { nixos.x = 1; includes = [ ... ]; } 16 19 ``` 17 20 18 - Includes owned configs, statics, and dispatches to functions via `atLeast`. 21 + Default uses `atLeast` matching. 22 + 23 + ### `den.lib.parametric.atLeast` 19 24 20 - ### parametric.atLeast 25 + Same as `parametric`. Functions match if all required params are present. 21 26 22 - Dispatches **only** to functions whose required args are a subset of context: 27 + ### `den.lib.parametric.exactly` 23 28 24 - **does include owned config, atLeast only forwards context**. 29 + Functions match only if required params exactly equal provided params. 25 30 26 31 ```nix 27 - F = parametric.atLeast { includes = [ a b ]; }; 32 + den.lib.parametric.exactly { includes = [ ({ host }: ...) ]; } 28 33 ``` 29 34 30 - ### parametric.exactly 35 + ### `den.lib.parametric.fixedTo` 31 36 32 - Dispatches **only** to functions whose args match context exactly: 33 - 34 - **does include owned config, atLeast only forwards context**. 37 + Calls the aspect with a fixed context, ignoring the actual context: 35 38 36 39 ```nix 37 - F = parametric.exactly { includes = [ a b ]; }; 40 + den.lib.parametric.fixedTo { host = myHost; } someAspect 38 41 ``` 39 42 40 - ### parametric.fixedTo 41 - 42 - Replaces context with a fixed attribute set: 43 + ### `den.lib.parametric.expands` 43 44 44 - Ignores any given context at call site, and replaces with a fixed one. 45 - Behaves like `parametric`, includes owned + static includes + function includes. 45 + Extends the received context with additional attributes before dispatch: 46 46 47 47 ```nix 48 - F = parametric.fixedTo { x = 1; } aspect; 48 + den.lib.parametric.expands { extra = true; } someAspect 49 49 ``` 50 50 51 - ### parametric.expands 51 + ### `den.lib.parametric.withOwn` 52 52 53 - Merges extra attributes into received context: 54 - Behaves like `parametric`, includes owned + static includes + function includes. 53 + Low-level constructor. Takes a `functor: self -> ctx -> aspect` and wraps 54 + an aspect so that owned configs and statics are included at the static 55 + stage, and the functor runs at the parametric stage. 55 56 56 - ```nix 57 - F = parametric.expands { extra = 1; } aspect; 58 - ``` 57 + ## `den.lib.canTake` 59 58 60 - ### parametric.withOwn 59 + Function argument introspection. 61 60 62 - Combinator: adds owned + statics on top of a dispatch functor: 63 - This is how `parametric` function itself uses `parametric.atLeast`. 61 + ### `den.lib.canTake params fn` 64 62 65 - ```nix 66 - # an exactly variant of parametric. 67 - F = parametric.withOwn parametric.exactly aspect; 68 - ``` 63 + Returns `true` if `fn`'s required arguments are satisfied by `params` (atLeast). 69 64 70 - ## den.lib.take 65 + ### `den.lib.canTake.atLeast params fn` 71 66 72 - Individual function matching: 67 + Same as `canTake`. 73 68 74 - ### take.atLeast 69 + ### `den.lib.canTake.exactly params fn` 75 70 76 - ```nix 77 - den.lib.take.atLeast ({ host, ... }: { nixos.x = 1; }) 78 - ``` 71 + Returns `true` only if `fn`'s required arguments exactly match `params`. 79 72 80 - Wraps function to only be called when context has at least the required args. 73 + ## `den.lib.take` 81 74 82 - Produces an empty attrset if the context has not atLeast the expected args. 75 + Conditional function application. 83 76 84 - ### take.exactly 77 + ### `den.lib.take.atLeast fn ctx` 85 78 86 - ```nix 87 - den.lib.take.exactly ({ host }: { nixos.x = 1; }) 88 - ``` 79 + Calls `fn ctx` if `canTake.atLeast ctx fn`, otherwise returns `{}`. 89 80 90 - Only called when context has exactly these args, no more. 81 + ### `den.lib.take.exactly fn ctx` 91 82 92 - Produces an empty attrset otherwise. 83 + Calls `fn ctx` if `canTake.exactly ctx fn`, otherwise returns `{}`. 93 84 94 - ### take.unused 85 + ### `den.lib.take.unused` 95 86 96 - ```nix 97 - den.lib.take.unused ignored_value result 98 - ``` 87 + `_unused: used: used` -- ignores first argument, returns second. Used for 88 + discarding `aspect-chain` in `import-tree`. 99 89 100 - Returns `result`, ignoring the first argument. Used internally. 90 + ## `den.lib.statics` 101 91 102 - ## den.lib.canTake 103 - 104 - Function signature introspection: 105 - 106 - Does not care about values, only about attribute names. 92 + Extracts only static includes from an aspect (non-function includes): 107 93 108 94 ```nix 109 - den.lib.canTake { x = 1; } someFunction 110 - # => true if someFunction can take at least { x } 111 - 112 - den.lib.canTake.atLeast { x = 1; } someFunction 113 - den.lib.canTake.exactly { x = 1; y = 2; } someFunction 95 + den.lib.statics someAspect { class = "nixos"; aspect-chain = []; } 114 96 ``` 115 97 116 - ## den.lib.aspects 98 + ## `den.lib.owned` 117 99 118 - Re-export of [`flake-aspects`](https://github.com/vic/flake-aspects) library. Provides: 119 - - `aspects.types.aspectsType` — module type for aspect trees 120 - - `aspects.types.providerType` — type for aspect providers 121 - - `aspects.forward` — class forwarding implementation 122 - 123 - ## den.lib.isFn 124 - 125 - Checks if a value is callable (function or attrset with `__functor`): 100 + Extracts owned configs from an aspect (removes `includes`, `__functor`): 126 101 127 102 ```nix 128 - den.lib.isFn someValue # => bool 103 + den.lib.owned someAspect 129 104 ``` 130 105 131 - ## den.lib.owned 106 + ## `den.lib.isFn` 132 107 133 - Extracts only owned configs from an aspect (**removes** `includes`, `__functor` from someAspect): 108 + Returns `true` if the value is a function or has `__functor`: 134 109 135 110 ```nix 136 - den.lib.owned someAspect # => { nixos = ...; darwin = ...; } 111 + den.lib.isFn myValue 137 112 ``` 138 113 139 - ## den.lib.statics 114 + ## `den.lib.isStatic` 140 115 141 - Creates a functor that only resolves static includes from an aspect: 142 - 143 - The new aspect ignores owned configs, and functional includes that are not 144 - of type (ground flake-aspects): `{ class, aspect-chain }: {...}` 116 + Returns `true` if the function can take `{ class, aspect-chain }`: 145 117 146 118 ```nix 147 - den.lib.statics someAspect # => <aspect with only static includes> 119 + den.lib.isStatic myFn 148 120 ``` 149 121 150 - ## den.lib.isStatic 122 + ## `den.lib.__findFile` 151 123 152 - Checks if a function requires only `{ class, aspect-chain }`: 124 + The angle bracket resolver. See [Angle Brackets Syntax](/guides/angle-brackets/). 153 125 154 126 ```nix 155 - den.lib.isStatic someFunction # => bool 127 + _module.args.__findFile = den.lib.__findFile; 156 128 ``` 157 129 158 - ## den.lib.__findFile 130 + ## `den.lib.aspects` 159 131 160 - Angle-bracket resolver. Translates `<path>` expressions to aspect lookups: 161 - 162 - ```nix 163 - _module.args.__findFile = den.lib.__findFile; 164 - # then: <foo/bar> => den.aspects.foo.provides.bar 165 - ``` 132 + The full [flake-aspects](https://github.com/vic/flake-aspects) API, 133 + initialized with the current `lib`. Provides `resolve`, `merge`, type 134 + definitions, and aspect manipulation functions.
+41 -63
docs/src/content/docs/reference/output.mdx
··· 1 1 --- 2 - title: Configuration Output 3 - description: How Den builds and outputs final NixOS, Darwin, and Home-Manager configurations. 2 + title: Output 3 + description: How Den builds flake outputs from host and home declarations. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 - <Aside title="Use the Source, Luke" icon="github">[`modules/config.nix`](https://github.com/vic/den/blob/main/modules/config.nix) · [`modules/output.nix`](https://github.com/vic/den/blob/main/modules/output.nix) · Tests: [`default-includes.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/default-includes.nix) · [`home-manager/`](https://github.com/vic/den/tree/main/templates/ci/modules/features/home-manager)</Aside> 8 + <Aside title="Source" icon="github"> 9 + [`modules/config.nix`](https://github.com/vic/den/blob/main/modules/config.nix) -- 10 + [`modules/output.nix`](https://github.com/vic/den/blob/main/modules/output.nix) 11 + </Aside> 9 12 10 - ## Overview 13 + ## Build pipeline 11 14 12 - Den transforms `den.hosts` and `den.homes` declarations into standard Nix 13 - configurations placed on `flake.nixosConfigurations`, `flake.darwinConfigurations`, 14 - and `flake.homeConfigurations`. 15 + Den converts `den.hosts` and `den.homes` declarations into flake outputs 16 + through a unified pipeline: 15 17 16 18 ```mermaid 17 19 graph LR 18 - H["den.hosts"] -->|"osConfiguration"| NC["flake.nixosConfigurations"] 19 - H -->|"osConfiguration"| DC["flake.darwinConfigurations"] 20 - HM["den.homes"] -->|"homeConfiguration"| HC["flake.homeConfigurations"] 20 + H[den.hosts] --> B[build] 21 + M[den.homes] --> B 22 + B --> F[flake.*Configurations] 21 23 ``` 22 24 23 - ## The Build Process 24 - 25 - For each entry in `den.hosts` and `den.homes`, Den: 26 - 27 - 1. **Resolves the main module** — applies the context pipeline to produce a 28 - single Nix module containing all class-specific configurations 29 - 2. **Calls the instantiate function** — passes the module to the platform builder 30 - 3. **Places the result** — puts it at `flake.<intoAttr>` 25 + ### Host instantiation 31 26 32 - ## OS Configurations 33 - 34 - Each host in `den.hosts.<system>.<name>` builds via: 27 + For each host in `den.hosts`, Den calls: 35 28 36 29 ```nix 37 30 host.instantiate { ··· 42 35 } 43 36 ``` 44 37 45 - The `mainModule` is the result of applying `den.ctx.host { host }` and 46 - resolving the aspect tree for the host's class. 38 + `host.mainModule` is internally computed by resolving the host's aspect 39 + with its context (`den.ctx.host`), collecting all class-specific config 40 + and dispatched includes. 41 + 42 + The result is placed at `flake.<intoAttr>` -- by default 43 + `flake.nixosConfigurations.<name>` or `flake.darwinConfigurations.<name>`. 47 44 48 - ## Home Configurations 45 + ### Home instantiation 49 46 50 - Each home in `den.homes.<system>.<name>` builds via: 47 + For each home in `den.homes`, Den calls: 51 48 52 49 ```nix 53 50 home.instantiate { ··· 56 53 } 57 54 ``` 58 55 59 - Where `home.pkgs` defaults to `inputs.nixpkgs.legacyPackages.<system>`. 56 + The result lands at `flake.homeConfigurations.<name>` by default. 60 57 61 - ## Output Placement 58 + ## `output.nix` -- flake-parts compatibility 62 59 63 - Results are folded into `flake` by `intoAttr`: 60 + When `inputs.flake-parts` is absent, Den defines its own `options.flake` 61 + option (adapted from flake-parts, MIT licensed) so that output generation 62 + works identically with or without flake-parts. 64 63 65 - | Class | Default `intoAttr` | 66 - |-------|-------------------| 67 - | `nixos` | `nixosConfigurations.<name>` | 68 - | `darwin` | `darwinConfigurations.<name>` | 69 - | `systemManager` | `systemConfigs.<name>` | 70 - | `homeManager` | `homeConfigurations.<name>` | 64 + This means Den can produce `nixosConfigurations`, `darwinConfigurations`, 65 + `homeConfigurations`, and any custom output attribute regardless of 66 + whether flake-parts is loaded. 71 67 72 - Override `intoAttr` to place configs elsewhere: 68 + ## Custom output paths 69 + 70 + Override `intoAttr` on any host or home to place outputs at a custom path: 73 71 74 72 ```nix 75 - den.homes.x86_64-linux.tux = { 76 - intoAttr = [ "homeConfigurations" "tux@igloo" ]; 73 + den.hosts.x86_64-linux.myhost = { 74 + intoAttr = [ "nixosConfigurations" "custom-name" ]; 77 75 }; 78 76 ``` 79 77 80 - ## Main Module Resolution 78 + ## Custom instantiation 81 79 82 - The bridge between Den's schema and final modules is the `mainModule`, 83 - computed internally as: 80 + Override `instantiate` to use a different builder or add `specialArgs`: 84 81 85 - ```mermaid 86 - graph TD 87 - Entity["host / home definition"] 88 - Entity -->|"ctx.host / ctx.home"| Intent["Apply context pipeline"] 89 - Intent -->|"aspect.resolve { class }"| Module["Obtain Nix module"] 90 - Module --> Instantiate["instantiate configuration"] 91 - Instantiate --> Output["set value into flake.<intoAttr>"] 82 + ```nix 83 + den.hosts.x86_64-linux.myhost = { 84 + instantiate = inputs.nixos-unstable.lib.nixosSystem; 85 + }; 92 86 ``` 93 - 94 - The context pipeline collects contributions from: 95 - - The entity's own aspect (host aspect or home aspect) 96 - - User aspects (for hosts with users) 97 - - `den.default` includes 98 - - Context-specific includes (`den.ctx.*.includes`) 99 - - Batteries and namespace aspects 100 - 101 - All contributions for the matching class are merged into one module. 102 - 103 - ## Without Flake-Parts 104 - 105 - When `inputs.flake-parts` is not present, Den provides a minimal 106 - `options.flake` definition so that configuration outputs have a place 107 - to be collected. This allows Den to work standalone or with any 108 - module evaluation system.
+81 -59
docs/src/content/docs/reference/schema.mdx
··· 1 1 --- 2 - title: Entity Schema Reference 3 - description: Host, user, and home configuration options. 2 + title: Schema 3 + description: Host, user, and home declaration options, base modules, and freeform types. 4 4 --- 5 5 6 6 import { Aside } from '@astrojs/starlight/components'; 7 7 8 + <Aside title="Source" icon="github"> 9 + [`modules/options.nix`](https://github.com/vic/den/blob/main/modules/options.nix) -- 10 + [`modules/_types.nix`](https://github.com/vic/den/blob/main/modules/_types.nix) 11 + </Aside> 8 12 9 - <Aside title="Use the Source, Luke" icon="github">[`modules/_types.nix`](https://github.com/vic/den/blob/main/modules/_types.nix) · [`modules/options.nix`](https://github.com/vic/den/blob/main/modules/options.nix) · Tests: [`host-options.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/host-options.nix) · [`schema-base-modules.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/schema-base-modules.nix) · [`homes.nix`](https://github.com/vic/den/blob/main/templates/ci/modules/features/homes.nix)</Aside> 13 + ## `den.hosts` 10 14 11 - `den.base` provides extensible schemas for `host`/`user`/`home`. 15 + Type: `attrsOf systemType` 12 16 13 - ## den.hosts 17 + Keyed by system string (e.g., `"x86_64-linux"`). Each system contains 18 + host definitions as freeform attribute sets. 14 19 15 20 ```nix 16 - den.hosts.<system>.<name> = { ... }; 21 + den.hosts.x86_64-linux.myhost = { 22 + users.vic = {}; 23 + }; 17 24 ``` 18 25 19 - ### Host Options 26 + ### Host options 20 27 21 28 | Option | Type | Default | Description | 22 29 |--------|------|---------|-------------| 23 - | `name` | str | attr name | Host configuration name | 24 - | `hostName` | str | `name` | Network hostname | 25 - | `system` | str | parent key | Platform (e.g., `x86_64-linux`) | 26 - | `class` | str | guessed | `nixos`, `darwin`, or `systemManager` | 27 - | `aspect` | str | `name` | Main aspect name | 28 - | `description` | str | auto | Human-readable description | 29 - | `users` | attrset | `{}` | User definitions | 30 - | `instantiate` | function | auto | Builder function | 31 - | `intoAttr` | str | auto | Flake output attribute | 32 - | `mainModule` | module | auto | Resolved NixOS/Darwin module | 33 - | *freeform* | anything | — | Custom attributes | 34 - 35 - ### Class Detection 36 - 37 - | System suffix | Default class | Default intoAttr | 38 - |--------------|---------------|------------------| 39 - | `*-linux` | `nixos` | `nixosConfigurations` | 40 - | `*-darwin` | `darwin` | `darwinConfigurations` | 30 + | `name` | `str` | attr name | Configuration name | 31 + | `hostName` | `str` | `name` | Network hostname | 32 + | `system` | `str` | parent key | Platform (e.g., `x86_64-linux`) | 33 + | `class` | `str` | auto | `"nixos"` or `"darwin"` based on system | 34 + | `aspect` | `str` | `name` | Main aspect name for this host | 35 + | `description` | `str` | auto | `class.hostName@system` | 36 + | `users` | `attrsOf userType` | `{}` | User accounts on this host | 37 + | `instantiate` | `raw` | auto | OS builder function | 38 + | `intoAttr` | `listOf str` | auto | Flake output path | 39 + | `*` | `den.base.host` options | | Options from base module | 40 + | `*` | | | free-form attributes | 41 41 42 - ### Instantiation 42 + ### `instantiate` defaults 43 43 44 - | Class | Default function | 45 - |-------|-----------------| 44 + | Class | Default | 45 + |-------|---------| 46 46 | `nixos` | `inputs.nixpkgs.lib.nixosSystem` | 47 47 | `darwin` | `inputs.darwin.lib.darwinSystem` | 48 48 | `systemManager` | `inputs.system-manager.lib.makeSystemConfig` | 49 49 50 - ## User Options 50 + ### `intoAttr` defaults 51 + 52 + | Class | Default | 53 + |-------|---------| 54 + | `nixos` | `[ "nixosConfigurations" name ]` | 55 + | `darwin` | `[ "darwinConfigurations" name ]` | 56 + | `systemManager` | `[ "systemConfigs" name ]` | 51 57 52 - ```nix 53 - den.hosts.<system>.<host>.users.<name> = { ... }; 54 - ``` 58 + ## `den.hosts.<sys>.<host>.users` 59 + 60 + Type: `attrsOf userType` 61 + 62 + ### User options 55 63 56 64 | Option | Type | Default | Description | 57 65 |--------|------|---------|-------------| 58 - | `name` | str | attr name | User configuration name | 59 - | `userName` | str | `name` | OS account name | 60 - | `class` | str | `homeManager` | Home management class | 61 - | `aspect` | str | `name` | Main aspect name | 62 - | *freeform* | anything | — | Custom attributes | 66 + | `name` | `str` | attr name | User configuration name | 67 + | `userName` | `str` | `name` | System account name | 68 + | `classes` | `listOf str` | `[ "homeManager" ]` | Home management classes | 69 + | `aspect` | `str` | `name` | Main aspect name | 70 + | `*` | `den.base.user` options | | Options from base module | 71 + | `*` | | | free-form attributes | 72 + 73 + Freeform: additional attributes pass through to the user module. 74 + 75 + ## `den.homes` 76 + 77 + Type: `attrsOf homeSystemType` 63 78 64 - ## den.homes 79 + Standalone home-manager configurations, keyed by system string. 65 80 66 81 ```nix 67 - den.homes.<system>.<name> = { ... }; 82 + den.homes.x86_64-linux.vic = {}; 68 83 ``` 69 84 70 - ### Home Options 85 + ### Home options 71 86 72 87 | Option | Type | Default | Description | 73 88 |--------|------|---------|-------------| 74 - | `name` | str | attr name | Home configuration name | 75 - | `userName` | str | `name` | User account name | 76 - | `system` | str | parent key | Platform system | 77 - | `class` | str | `homeManager` | Home management class | 78 - | `aspect` | str | `name` | Main aspect name | 79 - | `description` | str | auto | Human-readable description | 80 - | `pkgs` | attrset | auto | Nixpkgs instance | 81 - | `instantiate` | function | auto | Builder function | 82 - | `intoAttr` | str | `homeConfigurations` | Flake output attribute | 83 - | `mainModule` | module | auto | Resolved HM module | 84 - | *freeform* | anything | — | Custom attributes | 89 + | `name` | `str` | attr name | Home configuration name | 90 + | `userName` | `str` | `name` | User account name | 91 + | `system` | `str` | parent key | Platform system | 92 + | `class` | `str` | `"homeManager"` | Home management class | 93 + | `aspect` | `str` | `name` | Main aspect name | 94 + | `description` | `str` | auto | `home.userName@system` | 95 + | `pkgs` | `raw` | `inputs.nixpkgs.legacyPackages.$sys` | Nixpkgs instance | 96 + | `instantiate` | `raw` | `inputs.home-manager.lib.homeManagerConfiguration` | Builder | 97 + | `intoAttr` | `listOf str` | `[ "homeConfigurations" name ]` | Output path | 98 + | `*` | `den.base.home` options | | Options from base module | 99 + | `*` | | | free-form attributes | 100 + 101 + ## `den.base` 85 102 86 - ## den.base 103 + Base modules merged into all hosts, users, or homes. 87 104 88 - Base modules applied to all entities of a type: 105 + | Option | Type | Description | 106 + |--------|------|-------------| 107 + | `den.base.conf` | `deferredModule` | Applied to host, user, and home | 108 + | `den.base.host` | `deferredModule` | Applied to all hosts (imports `conf`) | 109 + | `den.base.user` | `deferredModule` | Applied to all users (imports `conf`) | 110 + | `den.base.home` | `deferredModule` | Applied to all homes (imports `conf`) | 89 111 90 112 ```nix 91 - den.base.conf = { ... }: { }; # hosts + users + homes 92 - den.base.host = { host, ... }: { }; 93 - den.base.user = { user, ... }: { }; 94 - den.base.home = { home, ... }: { }; 113 + den.base.conf = { lib, ... }: { 114 + # shared across all host/user/home declarations 115 + }; 116 + den.base.host = { ... }: { 117 + # host-specific base config 118 + }; 95 119 ``` 96 - 97 - `den.base.conf` is automatically imported by host, user, and home.
-9
docs/src/content/docs/sponsor.md
··· 1 - --- 2 - title: Sponsor Den development 3 - description: Become an sponsor and help Den development 4 - --- 5 - 6 - 7 - Den is made with love by [vic](https://bsky.app/profile/oeiuwq.bsky.social). 8 - 9 - If you find Den useful, consider [sponsoring](https://github.com/sponsors/vic).
+1 -1
docs/src/content/docs/tutorials/noflake.md
··· 126 126 127 127 ## Next Steps 128 128 129 - - Read [Use Without Flakes](/guides/no-flakes/) for more details on flake-free usage 129 + - Read [Core Principles](/explanation/core-principles/) to understand how Den works without flakes 130 130 - Consider the [Minimal template](/tutorials/minimal/) if you want flakes but not flake-parts
+1 -1
modules/_types.nix
··· 118 118 classes = lib.mkOption { 119 119 type = lib.types.listOf lib.types.str; 120 120 description = "home management nix classes"; 121 - default = [ "homeManager" ]; 121 + default = [ "user" ]; 122 122 }; 123 123 aspect = strOpt "main aspect name" config.name; 124 124 };
+23 -1
modules/aspects/provides/home-manager/hm-integration.nix
··· 43 43 44 44 in 45 45 { 46 - den.provides.home-manager = { }; 46 + den.provides.home-manager = 47 + _: 48 + throw '' 49 + NOTICE: den.provides.home-manager aspect is not used anymore. 50 + See https://den.oeiuwq.com/guides/home-manager/ 51 + 52 + Home Manager is now enabled via host config: 53 + 54 + # den.ctx.hm-host requires least one user with homeManager class. 55 + den.hosts.x86_64-linux.igloo.users.tux.classes = [ "homeManager" ]; 56 + 57 + Globally: 58 + 59 + den.base.user.classes = [ "homeManager" ]; 60 + 61 + See <den/home-manager/hm-os.nix> 62 + 63 + If you had includes at den._.home-manager, you can use: 64 + 65 + den.ctx.hm-host.includes = [ ... ]; 66 + 67 + For attaching aspects to home-manager enabled hosts. 68 + ''; 47 69 48 70 den.ctx.home.description = "Standalone Home-Manager config provided by home aspect"; 49 71 den.ctx.home._.home = { home }: parametric.fixedTo { inherit home; } den.aspects.${home.aspect};
+4 -1
templates/bogus/modules/bug.nix
··· 12 12 }: 13 13 { 14 14 # replace <system> if you are reporting a bug in MacOS 15 - den.hosts.x86_64-linux.igloo.users.tux = { }; 15 + den.hosts.x86_64-linux.igloo.users.tux.classes = [ 16 + "user" 17 + "homeManager" 18 + ]; 16 19 17 20 # do something for testing 18 21 den.aspects.tux.user.description = "The Penguin";
+1
templates/ci/modules/features/user-classes.nix
··· 22 22 23 23 expr = lib.sort (a: b: a < b) den.hosts.x86_64-linux.igloo.users.tux.classes; 24 24 expected = [ 25 + "homeManager" # this one comes from globally enabled at test support. 25 26 "homeManager" 26 27 "maid" 27 28 ];
+1
templates/ci/modules/test-support/eval-den.nix
··· 31 31 options.flake.packages = lib.mkOption { }; 32 32 options.expr = lib.mkOption { }; 33 33 options.expected = lib.mkOption { }; 34 + config.den.base.user.classes = [ "homeManager" ]; 34 35 }; 35 36 36 37 helpersModule =
+2 -2
templates/example/modules/den.nix
··· 1 1 { 2 - den.hosts.x86_64-linux.igloo.users.alice = { }; 3 - den.hosts.aarch64-darwin.apple.users.alice = { }; 2 + den.hosts.x86_64-linux.igloo.users.alice.classes = [ "homeManager" ]; 3 + den.hosts.aarch64-darwin.apple.users.alice.classes = [ "homeManager" ]; 4 4 den.homes.x86_64-linux.alice = { }; 5 5 }