···22222323- [Dendritic](https://github.com/mightyiam/dendritic): **same** concern across **different** Nix classes.
24242525-- Create [DRY](modules/aspects/provides/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) modules.
2525+- [Flake optional](templates/noflake). Works with _stable_/_unstable_ Nix and with/without flake-parts.
2626+2727+- Create [DRY](modules/aspects/provides/unfree/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) modules.
26282729- [Parametric](modules/aspects/provides/define-user.nix) over `host`/`home`/`user`.
28302929-- Context-aware [dependencies](modules/aspects/dependencies.nix): user/host contributions.
3131+- Context-aware [dependencies](modules/aspects/dependencies.nix): user\<->host bidirectional contributions.
30323131-- [templates/noflake](templates/noflake). Works with stable Nix (no-flakes) and needs no flake-parts.
3232-3333-- [Share](templates/default/modules/namespace.nix) aspects across systems & repos.
3333+- [Share](templates/example/modules/namespace.nix) aspects across systems & repos.
34343535-- [Routable](templates/default/modules/aspects/eg/routes.nix) configurations.
3535+- [Routable](templates/example/modules/aspects/eg/routes.nix) configurations.
36363737-- Custom factories for any Nix `class`.
3737+- Custom [factories](https://github.com/vic/den/blob/f5c44098e4855e07bf5cbcec00509e75ddde4220/templates/ci/modules/homes.nix#L20) for any Nix `class`.
38383939- Use `stable`/`unstable` channels per config.
40404141-- Freeform `host`/`user`/`home` [schemas](modules/_types.nix) (no `specialArgs`) with base modules.
4141+- Freeform `host`/`user`/`home` [schemas](modules/_types.nix) (no `specialArgs`) with [base](https://github.com/vic/den/pull/119) modules.
42424343- Multi-platform, multi-tenant hosts.
4444···46464747- Opt-in [`<angle/brackets>`](https://vic.github.io/den/angle-brackets.html) aspect resolution.
48484949-- Templates [tested](templates/default/modules/tests.nix) along [examples](templates/examples/modules/_example/ci).
4949+- Features [tested](templates/ci).
50505151-- Concepts [documented](https://vic.github.io/den).
5151+- REPL [friendly](https://github.com/vic/den/blob/f5c44098e4855e07bf5cbcec00509e75ddde4220/templates/bogus/modules/bug.nix#L34) [debugging](https://den.oeiuwq.com/debugging.html).
52525353Need more **batteries**? See [vic/denful](https://github.com/vic/denful).
5454···7777**Available templates**
78787979- [`default`](templates/default) batteries-included layout.
8080-- [`minimal`](templates/minimal) truly minimalistic start.
8181-- [`examples`](templates/examples) tests for all features.
8080+- [`minimal`](templates/minimal) minimalistic flake.
8181+- [`noflake`](templates/noflake) no flakes, no flake-parts, user nix-maid.
8282+- [`example`](templates/example) examples.
8383+- [`ci`](templates/ci) tests for all features.
8284- [`bogus`](templates/bogus) reproduce and report bugs.
83858486</div>
8587</td>
8688<td>
87898888-๐ Define [Hosts, Users](templates/examples/modules/_example/hosts.nix) & [Homes](templates/examples/modules/_example/homes.nix) concisely.
9090+### Den fundamental idea
9191+9292+> Configurations that can be applied to multiple host/user combinations.
9393+> The [`__functor`](https://den.oeiuwq.com/functor.html) pattern makes aspects parametric.
9494+9595+<details>
9696+9797+<summary>
9898+ Den is about propagating context to produce configs.
9999+</summary>
100100+101101+```nix
102102+# context => aspect
103103+{ host, user, ... }@context: {
104104+ # any Nix configuration classes
105105+ nixos = { };
106106+ darwin = { };
107107+ homeManager = { };
108108+ # supports conditional includes that inspect context
109109+ # unlike nix-module imports
110110+ includes = [ ];
111111+}
112112+```
113113+114114+You first define which [Hosts, Users](templates/ci/modules/hosts.nix)
115115+and [Homes](templates/ci/modules/homes.nix) exist
116116+using freeform-attributes or base-modules.
117117+118118+```nix
119119+{
120120+ # isWarm or any other freeform attr
121121+ den.hosts.x86_64-linux.igloo.isWarm = true;
122122+}
123123+```
891249090-See schema in [`_types.nix`](modules/_types.nix).
125125+Then, you write functions from context `host`/`user` to configs.
126126+127127+```nix
128128+{
129129+ den.aspects.heating = { host, user, ... }: {
130130+ nixos = { ... }; # depends on host.isWarm, etc.
131131+ homeManager = { ... };
132132+ };
133133+134134+ # previous aspect can be included on any host
135135+ # den.aspects.igloo.includes = [ den.aspects.heating ];
136136+ # or by default in all of them
137137+ # den.default.includes = [ den.aspects.heating ];
138138+}
139139+```
140140+141141+This way, configurations are truly re-usable,
142142+as they are nothing more than functions of the
143143+particularities of the host or its users.
144144+145145+</details>
146146+147147+### Code example
148148+149149+Schema based hosts/users/homes entities (see [`_types.nix`](modules/_types.nix)).
9115092151```nix
93152# modules/hosts.nix
94153{
9595- # same home-manager vic configuration
9696- # over laptop, macbook and standalone-hm
154154+ # same vic home-manager aspect shared
155155+ # on laptop, macbook and standalone-hm
97156 den.hosts.x86_64-linux.lap.users.vic = {};
98157 den.hosts.aarch64-darwin.mac.users.vic = {};
99158 den.homes.aarch64-darwin.vic = {};
···106165$ home-manager switch --flake .#vic
107166```
108167109109-๐งฉ [Aspect-oriented](https://github.com/vic/flake-aspects) incremental features. ([example](templates/default/modules/den.nix))
110110-111111-Any module can contribute configurations to aspects.
168168+Any module can contribute configurations to [aspects](https://github.com/vic/flake-aspects).
112169113170```nix
114171# modules/my-laptop.nix
···154211155212 };
156213}
214214+```
157215216216+```nix
158217# modules/vic.nix
159218{ den, ... }: {
160219 den.aspects.vic = {
···165224 };
166225 includes = [
167226 den.aspects.tiling-wm
168168- den._.primary-user
227227+ den.provides.primary-user
169228 ];
170229 };
171230}
···177236178237You are done! You know everything to start creating configurations with `den`.
179238180180-Feel free to to **explore** the codebase, particularly our [included batteries](modules/aspects/provides) and [tests](templates/examples/modules/_example/ci).
239239+Feel free to to **explore** the codebase, particularly our [included batteries](modules/aspects/provides) and [tests](templates/ci).
181240182241## Learn more at our [documentation website](https://vic.github.io/den)
183242
···11+{ inputs, lib, ... }:
22+if inputs ? flake-parts then
33+ { }
44+else
55+ {
66+ # NOTE: Currently Den needs a top-level attribute where to place configurations,
77+ # by default it is the `flake` attribute, even if Den uses no flake-parts at all.
88+ options.flake = lib.mkOption {
99+ type = lib.types.submodule { freeformType = lib.types.anything; };
1010+ };
1111+ }
···4455Create a **minimal** bug reproduction at [`modules/bug.nix`](modules/bug.nix)
6677+See also [Den debugging tips](https://den.oeiuwq.com/debugging.html)
88+79Then run tests:
81099-```console
1111+```shell
1012nix flake check
1113```
12141313-Format code with:
1515+Please share a link to your reproduction repo, showing the CI step and the
1616+error at CI build.
1717+1818+## Fixing Den
1919+2020+If you are contributing a bug-fix PR, you can use the following command to
2121+use your local den checkout.
14221515-```console
1616-nix fmt
2323+```shell
2424+cd <den-working-copy>
2525+nix flake check --override-input den . ./templates/bogus
1726```
···11-# DO-NOT-CHANGE. Keep your reproduction minimalistic!
22-#
33-# try not adding new inputs
44-# but if you have no options (pun intended)
55-# here's the place.
66-#
77-# IF you make any change to this file, use:
88-# `nix run .#write-flake`
99-#
1010-# We provide nix-darwin and home-manager for common usage.
1111-{
1212- # change "main" with a commit where bug is present
1313- flake-file.inputs.den.url = "github:vic/den/main";
1414-1515- # included so we can test HM integrations.
1616- flake-file.inputs.home-manager = {
1717- url = "github:nix-community/home-manager";
1818- inputs.nixpkgs.follows = "nixpkgs";
1919- };
2020-2121- # included for testing darwin hosts.
2222- flake-file.inputs.darwin = {
2323- url = "github:nix-darwin/nix-darwin";
2424- inputs.nixpkgs.follows = "nixpkgs";
2525- };
2626-}
···11+# configures class-automatic module auto imports for hosts/users/homes.
22+# See documentation at modules/aspects/provides/import-tree.nix
33+{
44+ # deadnix: skip
55+ __findFile ? __findFile,
66+ ...
77+}:
88+{
99+1010+ # alice imports non-dendritic <class> modules from ../non-dendritic/alice/_<class>/*.nix
1111+ den.aspects.alice.includes = [ (<den/import-tree> ./../non-dendritic/alice) ];
1212+1313+ # See the documentation at batteries/import-tree.nix
1414+ den.default.includes = [
1515+ (<den/import-tree/host> ./../non-dendritic/hosts)
1616+ (<den/import-tree/user> ./../non-dendritic/users)
1717+ (<den/import-tree/home> ./../non-dendritic/homes)
1818+ ];
1919+2020+ # tests
2121+ perSystem =
2222+ { checkCond, rockhopper, ... }:
2323+ {
2424+ checks.import-tree = checkCond "auto-imported from rockhopper/_nixos" (
2525+ rockhopper.config.auto-imported
2626+ );
2727+ };
2828+}
+76
templates/ci/modules/base-conf-modules.nix
···11+# tests for extending `den.base.*` modules with capabilities (options).
22+# these allow you to expend all hosts/users/homes with custom option
33+# that can later be used by aspects for providing features.
44+#
55+{ lib, ... }:
66+{
77+88+ # This module is base for all host configs.
99+ den.base.host =
1010+ { host, ... }:
1111+ {
1212+ options.capabilities.ssh-server = lib.mkEnableOption "Does host ${host.name} provide ssh?";
1313+ };
1414+1515+ # This module is base for all user configs.
1616+ den.base.user =
1717+ { user, ... }:
1818+ {
1919+ options.isAdmin = lib.mkOption {
2020+ type = lib.types.bool;
2121+ default = user.name == "alice"; # only alice is always admin
2222+ };
2323+ };
2424+2525+ # This module is base for all home configs.
2626+ # den.base.home = { home, ... }: { };
2727+2828+ # This one is included on each host/user/home
2929+ # it cannot access host/user/home values since this conf is generic.
3030+ den.base.conf = {
3131+ options.foo = lib.mkOption {
3232+ type = lib.types.str;
3333+ default = "bar";
3434+ };
3535+ };
3636+3737+ # Now hosts and users can set any option defined in base modules.
3838+ den.hosts.x86_64-linux.rockhopper = {
3939+ capabilities.ssh-server = true;
4040+ foo = "boo";
4141+ users.alice = {
4242+ # isAdmin = true; # alice is admin by default, nothing explicit here.
4343+ foo = "moo";
4444+ };
4545+ };
4646+4747+ den.aspects.rockhopper.includes =
4848+ let
4949+ # An aspect can make use of these options to provide configuration.
5050+ sshCapable =
5151+ { host, ... }:
5252+ {
5353+ nixos.services.sshd.enable = host.capabilities.ssh-server;
5454+ homeManager.services.ssh-agent.enable = host.capabilities.ssh-server;
5555+ };
5656+ in
5757+ [ sshCapable ];
5858+5959+ # CI checks
6060+ perSystem =
6161+ {
6262+ checkCond,
6363+ rockhopper,
6464+ alice-at-rockhopper,
6565+ ...
6666+ }:
6767+ {
6868+ checks.host-conf-rockhopper-sshd = checkCond "sshd enabled" (
6969+ rockhopper.config.services.sshd.enable == true
7070+ );
7171+ checks.host-conf-alice-sshd = checkCond "ssh-agent enabled" (
7272+ alice-at-rockhopper.services.ssh-agent.enable == true
7373+ );
7474+ };
7575+7676+}
···11+# User TODO: Remove this file.
22+{
33+ # default aspect can be used for global static settings.
44+ den.default = {
55+ # static values.
66+ darwin.system.stateVersion = 6;
77+ nixos.system.stateVersion = "25.05";
88+ homeManager.home.stateVersion = "25.05";
99+1010+ # these defaults are set for checking with CI.
1111+ nixos.programs.vim.enable = true;
1212+ darwin.programs.zsh.enable = true;
1313+ };
1414+}
+27
templates/ci/modules/define-user.nix
···11+{ den, ... }:
22+{
33+ den.default.includes = [
44+ # Example: parametric over many contexts: { home }, { host, user }, { fromUser, toHost }
55+ den.provides.define-user
66+ ];
77+88+ perSystem =
99+ {
1010+ checkCond,
1111+ rockhopper,
1212+ adelie,
1313+ ...
1414+ }:
1515+ {
1616+1717+ checks.alice-exists-on-rockhopper = checkCond "den.default.user.includes defines user on host" (
1818+ rockhopper.config.users.users.alice.isNormalUser
1919+ );
2020+ checks.alice-not-exists-on-adelie = checkCond "den.default.user.includes defines user on host" (
2121+ !adelie.config.users.users ? alice
2222+ );
2323+ checks.will-exists-on-adelie = checkCond "den.default.user.includes defines user on host" (
2424+ adelie.config.users.users.will.isNormalUser
2525+ );
2626+ };
2727+}
···11+# this is a non-dendritic darwin class module file.
22+# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
33+{ ... }:
44+{
55+ # see nix-darwin options.
66+}
···11+# this is a non-dendritic nix class module file.
22+# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
33+#
44+# USER TODO: Remove this file.
55+# suppose this file was auto-generated by nixos-generate-config or some other hardware tooling.
66+{ lib, ... }:
77+{
88+ # used in CI to test this file was actually imported.
99+ options.auto-imported = lib.mkOption {
1010+ readOnly = true;
1111+ type = lib.types.bool;
1212+ default = true;
1313+ };
1414+}
···1616nix flake check
1717```
18181919-- Read [modules/den.nix](modules/den.nix) where hosts and homes definitions are for this example.
2020-2121-- Read [modules/namespace.nix](modules/namespace.nix) where a new `eg` (an example) aspects namespace is created.
2222-2323-- Read [modules/aspects/igloo.nix](modules/aspects/igloo.nix) where the `igloo` host is configured.
2424-2525-- Read [modules/aspects/alice.nix](modules/aspects/alice.nix) where the `alice` user is configured.
2626-2727-- Run the VM.
2828-2929-```console
3030-nix run .#vm
3131-```
3232-3333-- Edit and run VM loop.
3434-3535-Feel free to add more aspects, organize things to your liking.
1919+- Edit [modules/hosts.nix](modules/hosts.nix)
···11-{ den, eg, ... }:
22-{
33- den.aspects.alice = {
44-55- # Alice can include other aspects.
66- # For small, private one-shot aspects, use let-bindings like here.
77- # for more complex or re-usable ones, define on their own modules,
88- # as part of any aspect-subtree.
99- includes =
1010- let
1111- # hack for nixf linter to keep findFile :/
1212- unused = den.lib.take.unused __findFile;
1313- __findFile = unused den.lib.__findFile;
1414-1515- customEmacs.homeManager =
1616- { pkgs, ... }:
1717- {
1818- programs.emacs.enable = true;
1919- programs.emacs.package = pkgs.emacs30-nox;
2020- };
2121- in
2222- [
2323- # from local bindings.
2424- customEmacs
2525- # from the aspect tree, cooper example is defined bellow
2626- den.aspects.cooper
2727- den.aspects.setHost
2828- # from the `eg` namespace.
2929- eg.autologin
3030- # den included batteries that provide common configs.
3131- <den/primary-user> # alice is admin always.
3232- (<den/user-shell> "fish") # default user shell
3333- ];
3434-3535- # Alice configures NixOS hosts it lives on.
3636- nixos =
3737- { pkgs, ... }:
3838- {
3939- users.users.alice.packages = [ pkgs.vim ];
4040- };
4141-4242- # Alice home-manager.
4343- homeManager =
4444- { pkgs, ... }:
4545- {
4646- home.packages = [ pkgs.htop ];
4747- };
4848-4949- # <user>.provides.<host>, via eg/routes.nix
5050- provides.igloo =
5151- { host, ... }:
5252- {
5353- nixos.programs.nh.enable = host.name == "igloo";
5454- };
5555- };
5656-5757- # This is a context-aware aspect, that emits configurations
5858- # **anytime** at least the `user` data is in context.
5959- # read more at https://vic.github.io/den/context-aware.html
6060- den.aspects.cooper =
6161- { user, ... }:
6262- {
6363- nixos.users.users.${user.userName}.description = "Alice Cooper";
6464- };
6565-6666- den.aspects.setHost =
6767- { host, ... }:
6868- {
6969- networking.hostName = host.hostName;
7070- };
7171-}
-47
templates/default/modules/aspects/defaults.nix
···11-{
22- config,
33- # deadnix: skip # enable <den/brackets> syntax for demo.
44- __findFile ? __findFile,
55- den,
66- ...
77-}:
88-{
99- # Lets also configure some defaults using aspects.
1010- # These are global static settings.
1111- den.default = {
1212- darwin.system.stateVersion = 6;
1313- nixos.system.stateVersion = "25.05";
1414- homeManager.home.stateVersion = "25.05";
1515- };
1616-1717- # These are functions that produce configs
1818- den.default.includes = [
1919- # ${user}.provides.${host} and ${host}.provides.${user}
2020- <eg/routes>
2121-2222- # Enable home-manager on all hosts.
2323- <den/home-manager>
2424-2525- # Automatically create the user on host.
2626- <den/define-user>
2727-2828- # Disable booting when running on CI on all NixOS hosts.
2929- (if config ? _module.args.CI then <eg/ci-no-boot> else { })
3030-3131- # NOTE: be cautious when adding fully parametric functions to defaults.
3232- # defaults are included on EVERY host/user/home, and IF you are not careful
3333- # you could be duplicating config values. For example:
3434- #
3535- # # This will append 42 into foo option for the {host} and for EVERY {host,user}
3636- # ({ host, ... }: { nixos.foo = [ 42 ]; }) # DO-NOT-DO-THIS.
3737- #
3838- # # Instead try to be explicit if a function is intended for ONLY { host }.
3939- (den.lib.take.exactly (
4040- { OS, host }:
4141- den.lib.take.unused OS {
4242- nixos.networking.hostName = host.hostName;
4343- }
4444- ))
4545-4646- ];
4747-}
···11-{
22- # autologin is context-aware, parametric aspect.
33- # it applies only if the context has at least { user }
44- # meaning that has access to user data
55- eg.autologin =
66- { user, ... }:
77- {
88- nixos =
99- { config, lib, ... }:
1010- lib.mkIf config.services.displayManager.enable {
1111- services.displayManager.autoLogin.enable = true;
1212- services.displayManager.autoLogin.user = user.userName;
1313- };
1414- };
1515-}
···11-# This example implements an aspect "routing" pattern.
22-#
33-# Unlike `den.default` which is `parametric.atLeast`
44-# we use `parametric.fixedTo` here, which help us
55-# propagate an already computed context to all includes.
66-#
77-# This aspect, when installed in a `parametric.atLeast`
88-# will just forward the same context.
99-# The `mutual` helper returns an static configuration which
1010-# is ignored by parametric aspects, thus allowing
1111-# non-existing aspects to be just ignored.
1212-#
1313-# Be sure to read: https://vic.github.io/den/dependencies.html
1414-# See usage at: defaults.nix, alice.nix, igloo.nix
1515-#
1616-{ den, ... }:
1717-{
1818- # Usage: `den.default.includes [ eg.routes ]`
1919- eg.routes =
2020- let
2121- inherit (den.lib) parametric;
2222-2323- # eg, `<user>._.<host>` and `<host>._.<user>`
2424- mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { };
2525-2626- routes =
2727- { host, user, ... }@ctx:
2828- parametric.fixedTo ctx {
2929- includes = [
3030- (mutual user host)
3131- (mutual host user)
3232- ];
3333- };
3434- in
3535- routes;
3636-}
···11-{ inputs, lib, ... }:
11+{ inputs, ... }:
22{
33- flake-file.inputs.flake-file.url = lib.mkDefault "github:vic/flake-file";
44- flake-file.inputs.den.url = lib.mkDefault "github:vic/den";
53 imports = [
64 (inputs.flake-file.flakeModules.dendritic or { })
75 (inputs.den.flakeModules.dendritic or { })
86 ];
77+88+ # other inputs may be defined at a module using them.
99+ flake-file.inputs = {
1010+ den.url = "github:vic/den";
1111+ flake-file.url = "github:vic/flake-file";
1212+ home-manager = {
1313+ url = "github:nix-community/home-manager";
1414+ inputs.nixpkgs.follows = "nixpkgs";
1515+ };
1616+ };
917}
+19
templates/default/modules/hosts.nix
···11+# defines all hosts + users + homes.
22+# then config their aspects in as many files you want
33+{
44+ # tux user at igloo host.
55+ den.hosts.x86_64-linux.igloo.users.tux = { };
66+77+ # define an standalone home-manager for tux
88+ # den.homes.x86_64-linux.tux = { };
99+1010+ # be sure to add nix-darwin input for this:
1111+ # den.hosts.aarch64-darwin.apple.users.alice = { };
1212+1313+ # other hosts can also have user tux.
1414+ # den.hosts.x86_64-linux.south = {
1515+ # wsl = { }; # add nixos-wsl input for this.
1616+ # users.tux = { };
1717+ # users.orca = { };
1818+ # };
1919+}
···11-# This repo was generated with github:vic/flake-file#dendritic template.
22-# Run `nix run .#write-flake` after changing any input.
33-#
44-# Inputs can be placed in any module, the best practice is to have them
55-# as close as possible to their actual usage.
66-# See: https://vic.github.io/dendrix/Dendritic.html#minimal-and-focused-flakenix
77-#
88-# For our template, we enable home-manager and nix-darwin by default, but
99-# you are free to remove them if not being used by you.
1010-{ ... }:
1111-{
1212-1313- flake-file.inputs = {
1414- home-manager = {
1515- url = "github:nix-community/home-manager";
1616- inputs.nixpkgs.follows = "nixpkgs";
1717- };
1818-1919- darwin = {
2020- url = "github:nix-darwin/nix-darwin";
2121- inputs.nixpkgs.follows = "nixpkgs";
2222- };
2323-2424- ## these stable inputs are for wsl
2525- #nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
2626- #home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
2727- #home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
2828-2929- #nixos-wsl = {
3030- # url = "github:nix-community/nixos-wsl";
3131- # inputs.nixpkgs.follows = "nixpkgs-stable";
3232- # inputs.flake-compat.follows = "";
3333- #};
3434-3535- };
3636-3737-}
-15
templates/default/modules/namespace.nix
···11-{ inputs, den, ... }:
22-{
33- # create an `eg` (example!) namespace. (flake exposed)
44- imports = [ (inputs.den.namespace "eg" true) ];
55-66- # you can have more than one namespace (false = not flake exposed)
77- # imports = [ (inputs.den.namespace "my" false) ];
88-99- # you can also merge many namespaces from remote flakes.
1010- # keep in mind a namespace is defined only once, so give it an array:
1111- # imports = [ (inputs.den.namespace "ours" [inputs.ours inputs.theirs]) ];
1212-1313- # this line enables den angle brackets syntax in modules.
1414- _module.args.__findFile = den.lib.__findFile;
1515-}
···11+{ den, ... }:
22+{
33+ # user aspect
44+ den.aspects.tux = {
55+ includes = [
66+ den.provides.primary-user
77+ (den.provides.user-shell "fish")
88+ ];
99+1010+ homeManager =
1111+ { pkgs, ... }:
1212+ {
1313+ home.packages = [ pkgs.htop ];
1414+ };
1515+1616+ # user can provide NixOS configurations
1717+ # to any host it is included on
1818+ # nixos = { pkgs, ... }: { };
1919+ };
2020+}
-22
templates/default/modules/vm.nix
···11-# enables `nix run .#vm`. it is very useful to have a VM
22-# you can edit your config and launch the VM to test stuff
33-# instead of having to reboot each time.
44-{ inputs, eg, ... }:
55-{
66-77- den.aspects.igloo.includes = [
88- eg.vm._.gui
99- # eg.vm._.tui
1010- ];
1111-1212- perSystem =
1313- { pkgs, ... }:
1414- {
1515- packages.vm = pkgs.writeShellApplication {
1616- name = "vm";
1717- text = ''
1818- ${inputs.self.nixosConfigurations.igloo.config.system.build.vm}/bin/run-igloo-vm "$@"
1919- '';
2020- };
2121- };
2222-}
···11+# Examples
22+33+This template provides some basic examples of how to use Den features.
44+However, you will learn more by reading templates/ci which tests all of Den.
55+66+Steps you can follow after cloning this template:
77+88+- Be sure to read the [den documentation](https://vic.github.io/den)
99+1010+- Update den input.
1111+1212+```console
1313+nix flake update den
1414+```
1515+1616+- Run checks to test everything works.
1717+1818+```console
1919+nix flake check
2020+```
2121+2222+- Read [modules/den.nix](modules/den.nix) where hosts and homes definitions are for this example.
2323+2424+- Read [modules/namespace.nix](modules/namespace.nix) where a new `eg` (an example) aspects namespace is created.
2525+2626+- Read [modules/aspects/igloo.nix](modules/aspects/igloo.nix) where the `igloo` host is configured.
2727+2828+- Read [modules/aspects/alice.nix](modules/aspects/alice.nix) where the `alice` user is configured.
2929+3030+- Run the VM.
3131+3232+```console
3333+nix run .#vm
3434+```
3535+3636+- Edit and run VM loop.
3737+3838+Feel free to add more aspects, organize things to your liking.
···11+{ den, eg, ... }:
22+{
33+ den.aspects.alice = {
44+55+ # Alice can include other aspects.
66+ # For small, private one-shot aspects, use let-bindings like here.
77+ # for more complex or re-usable ones, define on their own modules,
88+ # as part of any aspect-subtree.
99+ includes =
1010+ let
1111+ # hack for nixf linter to keep findFile :/
1212+ unused = den.lib.take.unused __findFile;
1313+ __findFile = unused den.lib.__findFile;
1414+1515+ customEmacs.homeManager =
1616+ { pkgs, ... }:
1717+ {
1818+ programs.emacs.enable = true;
1919+ programs.emacs.package = pkgs.emacs30-nox;
2020+ };
2121+ in
2222+ [
2323+ # from local bindings.
2424+ customEmacs
2525+ # from the aspect tree, cooper example is defined bellow
2626+ den.aspects.cooper
2727+ den.aspects.setHost
2828+ # from the `eg` namespace.
2929+ eg.autologin
3030+ # den included batteries that provide common configs.
3131+ <den/primary-user> # alice is admin always.
3232+ (<den/user-shell> "fish") # default user shell
3333+ ];
3434+3535+ # Alice configures NixOS hosts it lives on.
3636+ nixos =
3737+ { pkgs, ... }:
3838+ {
3939+ users.users.alice.packages = [ pkgs.vim ];
4040+ };
4141+4242+ # Alice home-manager.
4343+ homeManager =
4444+ { pkgs, ... }:
4545+ {
4646+ home.packages = [ pkgs.htop ];
4747+ };
4848+4949+ # <user>.provides.<host>, via eg/routes.nix
5050+ provides.igloo =
5151+ { host, ... }:
5252+ {
5353+ nixos.programs.nh.enable = host.name == "igloo";
5454+ };
5555+ };
5656+5757+ # This is a context-aware aspect, that emits configurations
5858+ # **anytime** at least the `user` data is in context.
5959+ # read more at https://vic.github.io/den/context-aware.html
6060+ den.aspects.cooper =
6161+ { user, ... }:
6262+ {
6363+ nixos.users.users.${user.userName}.description = "Alice Cooper";
6464+ };
6565+6666+ den.aspects.setHost =
6767+ { host, ... }:
6868+ {
6969+ networking.hostName = host.hostName;
7070+ };
7171+}
+47
templates/example/modules/aspects/defaults.nix
···11+{
22+ config,
33+ # deadnix: skip # enable <den/brackets> syntax for demo.
44+ __findFile ? __findFile,
55+ den,
66+ ...
77+}:
88+{
99+ # Lets also configure some defaults using aspects.
1010+ # These are global static settings.
1111+ den.default = {
1212+ darwin.system.stateVersion = 6;
1313+ nixos.system.stateVersion = "25.05";
1414+ homeManager.home.stateVersion = "25.05";
1515+ };
1616+1717+ # These are functions that produce configs
1818+ den.default.includes = [
1919+ # ${user}.provides.${host} and ${host}.provides.${user}
2020+ <eg/routes>
2121+2222+ # Enable home-manager on all hosts.
2323+ <den/home-manager>
2424+2525+ # Automatically create the user on host.
2626+ <den/define-user>
2727+2828+ # Disable booting when running on CI on all NixOS hosts.
2929+ (if config ? _module.args.CI then <eg/ci-no-boot> else { })
3030+3131+ # NOTE: be cautious when adding fully parametric functions to defaults.
3232+ # defaults are included on EVERY host/user/home, and IF you are not careful
3333+ # you could be duplicating config values. For example:
3434+ #
3535+ # # This will append 42 into foo option for the {host} and for EVERY {host,user}
3636+ # ({ host, ... }: { nixos.foo = [ 42 ]; }) # DO-NOT-DO-THIS.
3737+ #
3838+ # # Instead try to be explicit if a function is intended for ONLY { host }.
3939+ (den.lib.take.exactly (
4040+ { OS, host }:
4141+ den.lib.take.unused OS {
4242+ nixos.networking.hostName = host.hostName;
4343+ }
4444+ ))
4545+4646+ ];
4747+}
···11+{
22+ # autologin is context-aware, parametric aspect.
33+ # it applies only if the context has at least { user }
44+ # meaning that has access to user data
55+ eg.autologin =
66+ { user, ... }:
77+ {
88+ nixos =
99+ { config, lib, ... }:
1010+ lib.mkIf config.services.displayManager.enable {
1111+ services.displayManager.autoLogin.enable = true;
1212+ services.displayManager.autoLogin.user = user.userName;
1313+ };
1414+ };
1515+}
···11+# This example implements an aspect "routing" pattern.
22+#
33+# Unlike `den.default` which is `parametric.atLeast`
44+# we use `parametric.fixedTo` here, which help us
55+# propagate an already computed context to all includes.
66+#
77+# This aspect, when installed in a `parametric.atLeast`
88+# will just forward the same context.
99+# The `mutual` helper returns an static configuration which
1010+# is ignored by parametric aspects, thus allowing
1111+# non-existing aspects to be just ignored.
1212+#
1313+# Be sure to read: https://vic.github.io/den/dependencies.html
1414+# See usage at: defaults.nix, alice.nix, igloo.nix
1515+#
1616+{ den, ... }:
1717+{
1818+ # Usage: `den.default.includes [ eg.routes ]`
1919+ eg.routes =
2020+ let
2121+ inherit (den.lib) parametric;
2222+2323+ # eg, `<user>._.<host>` and `<host>._.<user>`
2424+ mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { };
2525+2626+ routes =
2727+ { host, user, ... }@ctx:
2828+ parametric.fixedTo ctx {
2929+ includes = [
3030+ (mutual user host)
3131+ (mutual host user)
3232+ ];
3333+ };
3434+ in
3535+ routes;
3636+}
···11+# This repo was generated with github:vic/flake-file#dendritic template.
22+# Run `nix run .#write-flake` after changing any input.
33+#
44+# Inputs can be placed in any module, the best practice is to have them
55+# as close as possible to their actual usage.
66+# See: https://vic.github.io/dendrix/Dendritic.html#minimal-and-focused-flakenix
77+#
88+# For our template, we enable home-manager and nix-darwin by default, but
99+# you are free to remove them if not being used by you.
1010+{ ... }:
1111+{
1212+1313+ flake-file.inputs = {
1414+ home-manager = {
1515+ url = "github:nix-community/home-manager";
1616+ inputs.nixpkgs.follows = "nixpkgs";
1717+ };
1818+1919+ darwin = {
2020+ url = "github:nix-darwin/nix-darwin";
2121+ inputs.nixpkgs.follows = "nixpkgs";
2222+ };
2323+2424+ ## these stable inputs are for wsl
2525+ #nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
2626+ #home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
2727+ #home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
2828+2929+ #nixos-wsl = {
3030+ # url = "github:nix-community/nixos-wsl";
3131+ # inputs.nixpkgs.follows = "nixpkgs-stable";
3232+ # inputs.flake-compat.follows = "";
3333+ #};
3434+3535+ };
3636+3737+}
+15
templates/example/modules/namespace.nix
···11+{ inputs, den, ... }:
22+{
33+ # create an `eg` (example!) namespace. (flake exposed)
44+ imports = [ (inputs.den.namespace "eg" true) ];
55+66+ # you can have more than one namespace (false = not flake exposed)
77+ # imports = [ (inputs.den.namespace "my" false) ];
88+99+ # you can also merge many namespaces from remote flakes.
1010+ # keep in mind a namespace is defined only once, so give it an array:
1111+ # imports = [ (inputs.den.namespace "ours" [inputs.ours inputs.theirs]) ];
1212+1313+ # this line enables den angle brackets syntax in modules.
1414+ _module.args.__findFile = den.lib.__findFile;
1515+}
···11+# enables `nix run .#vm`. it is very useful to have a VM
22+# you can edit your config and launch the VM to test stuff
33+# instead of having to reboot each time.
44+{ inputs, eg, ... }:
55+{
66+77+ den.aspects.igloo.includes = [
88+ eg.vm._.gui
99+ # eg.vm._.tui
1010+ ];
1111+1212+ perSystem =
1313+ { pkgs, ... }:
1414+ {
1515+ packages.vm = pkgs.writeShellApplication {
1616+ name = "vm";
1717+ text = ''
1818+ ${inputs.self.nixosConfigurations.igloo.config.system.build.vm}/bin/run-igloo-vm "$@"
1919+ '';
2020+ };
2121+ };
2222+}
···11-User TODO: REMOVE this directory (or disable its import from den.nix)
22-33-It is used to implement tests for all den features so we can validate at CI.
44-55-Use it as reference to see how den features are used, however, be aware that this might not be the best practices for file/aspect organization.
···11-# this is a non-dendritic darwin class module file.
22-# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
33-{ ... }:
44-{
55- # see nix-darwin options.
66-}
···11-# this is a non-dendritic nix class module file.
22-# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
33-#
44-# USER TODO: Remove this file.
55-# suppose this file was auto-generated by nixos-generate-config or some other hardware tooling.
66-{ lib, ... }:
77-{
88- # used in CI to test this file was actually imported.
99- options.auto-imported = lib.mkOption {
1010- readOnly = true;
1111- type = lib.types.bool;
1212- default = true;
1313- };
1414-}
···11-# tests for extending `den.base.*` modules with capabilities (options).
22-# these allow you to expend all hosts/users/homes with custom option
33-# that can later be used by aspects for providing features.
44-#
55-{ lib, ... }:
66-{
77-88- # This module is base for all host configs.
99- den.base.host =
1010- { host, ... }:
1111- {
1212- options.capabilities.ssh-server = lib.mkEnableOption "Does host ${host.name} provide ssh?";
1313- };
1414-1515- # This module is base for all user configs.
1616- den.base.user =
1717- { user, ... }:
1818- {
1919- options.isAdmin = lib.mkOption {
2020- type = lib.types.bool;
2121- default = user.name == "alice"; # only alice is always admin
2222- };
2323- };
2424-2525- # This module is base for all home configs.
2626- # den.base.home = { home, ... }: { };
2727-2828- # This one is included on each host/user/home
2929- # it cannot access host/user/home values since this conf is generic.
3030- den.base.conf = {
3131- options.foo = lib.mkOption {
3232- type = lib.types.str;
3333- default = "bar";
3434- };
3535- };
3636-3737- # Now hosts and users can set any option defined in base modules.
3838- den.hosts.x86_64-linux.rockhopper = {
3939- capabilities.ssh-server = true;
4040- foo = "boo";
4141- users.alice = {
4242- # isAdmin = true; # alice is admin by default, nothing explicit here.
4343- foo = "moo";
4444- };
4545- };
4646-4747- den.aspects.rockhopper.includes =
4848- let
4949- # An aspect can make use of these options to provide configuration.
5050- sshCapable =
5151- { host, ... }:
5252- {
5353- nixos.services.sshd.enable = host.capabilities.ssh-server;
5454- homeManager.services.ssh-agent.enable = host.capabilities.ssh-server;
5555- };
5656- in
5757- [ sshCapable ];
5858-5959- # CI checks
6060- perSystem =
6161- {
6262- checkCond,
6363- rockhopper,
6464- alice-at-rockhopper,
6565- ...
6666- }:
6767- {
6868- checks.host-conf-rockhopper-sshd = checkCond "sshd enabled" (
6969- rockhopper.config.services.sshd.enable == true
7070- );
7171- checks.host-conf-alice-sshd = checkCond "ssh-agent enabled" (
7272- alice-at-rockhopper.services.ssh-agent.enable == true
7373- );
7474- };
7575-7676-}
···11-# User TODO: Remove this file.
22-{
33- # default aspect can be used for global static settings.
44- den.default = {
55- # static values.
66- darwin.system.stateVersion = 6;
77- nixos.system.stateVersion = "25.05";
88- homeManager.home.stateVersion = "25.05";
99-1010- # these defaults are set for checking with CI.
1111- nixos.programs.vim.enable = true;
1212- darwin.programs.zsh.enable = true;
1313- };
1414-}
···11-# Example standalone home-manager configurations.
22-# These are independent of any host configuration.
33-# See documentation at <den>/nix/types.nix
44-{ inputs, ... }:
55-{
66- den.homes.x86_64-linux.cam = { };
77-88- den.homes.aarch64-darwin.bob = {
99- userName = "robert";
1010- aspect = "developer";
1111- };
1212-1313- # Example: custom home-manager instantiate for passing extraSpecialArgs.
1414- den.homes.x86_64-linux.luke =
1515- let
1616- osConfig = inputs.self.nixosConfigurations.rockhopper.config;
1717- in
1818- {
1919- # Example: luke standalone-homemanager needs access to rockhopper osConfig.
2020- instantiate =
2121- { pkgs, modules }:
2222- inputs.home-manager.lib.homeManagerConfiguration {
2323- inherit pkgs modules;
2424- extraSpecialArgs.osConfig = osConfig;
2525- };
2626-2727- # Example: custom attribute instead of specialArgs
2828- programToDependOn = "vim";
2929- };
3030-3131-}
-50
templates/examples/modules/_example/hosts.nix
···11-# This is a fully working example configuration.
22-# Feel free to remove it, adapt or split into several modules.
33-# See documentation at <den>/nix/types.nix
44-{ inputs, ... }:
55-{
66- den.hosts.aarch64-darwin.honeycrisp.users.alice = { };
77-88- den.hosts.aarch64-linux.emperor.users.alice = { };
99-1010- den.hosts.x86_64-linux = {
1111- rockhopper = {
1212- description = "rockhopper is a kind of penguin";
1313- users.alice = { };
1414- };
1515-1616- adelie = {
1717- description = "wsl on windows";
1818- users.will = { };
1919- intoAttr = "wslConfigurations";
2020- # custom nixpkgs channel.
2121- instantiate = inputs.nixpkgs-stable.lib.nixosSystem;
2222- # custom attribute (see home-manager.nix)
2323- hm-module = inputs.home-manager-stable.nixosModules.home-manager;
2424- # custom attribute (see primary-user.nix)
2525- wsl = { };
2626- };
2727- };
2828-2929- # move these inputs to any module you want.
3030- # they are here for all our examples to work on CI.
3131- flake-file.inputs = {
3232-3333- # these stable inputs are for wsl
3434- nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
3535- home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
3636- home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
3737-3838- nixos-wsl = {
3939- url = "github:nix-community/nixos-wsl";
4040- inputs.nixpkgs.follows = "nixpkgs-stable";
4141- inputs.flake-compat.follows = "";
4242- };
4343-4444- darwin = {
4545- url = "github:nix-darwin/nix-darwin";
4646- inputs.nixpkgs.follows = "nixpkgs";
4747- };
4848- };
4949-5050-}
-10
templates/examples/modules/den.nix
···11-# USER TODO: remove this file.
22-# copy any desired module to your ./modules and let it be auto-imported.
33-{ inputs, ... }:
44-{
55- imports = [
66- # The _example directory contains CI tests for all den features.
77- # use it as reference of usage, but not of best practices.
88- (inputs.import-tree ./_example)
99- ];
1010-}
···11{ inputs, den, ... }:
22{
33- systems = builtins.attrNames den.hosts;
33+ # we can import this flakeModule even if we dont have flake-parts as input!
44 imports = [ inputs.den.flakeModule ];
5566 den.hosts.x86_64-linux.igloo.users.tux = { };
77- den.aspects.igloo = { };
77+88+ den.aspects.igloo = {
99+ nixos =
1010+ { pkgs, ... }:
1111+ {
1212+ environment.systemPackages = [ pkgs.hello ];
1313+ passthru = { };
1414+ };
1515+ };
1616+1717+ den.aspects.tux = {
1818+ includes = [ den.provides.primary-user ];
1919+ };
820}
-11
templates/noflake/modules/compat.nix
···11-{ inputs, lib, ... }:
22-{
33- # we can import this flakeModule even if we dont have flake-parts as input!
44- imports = [ inputs.den.flakeModule ];
55-66- # NOTE: Currently Den needs a top-level attribute where to place configurations,
77- # by default it is the `flake` attribute, even if Den uses no flake-parts at all.
88- options.flake = lib.mkOption {
99- type = lib.types.submodule { freeformType = lib.types.anything; };
1010- };
1111-}
+3-4
templates/noflake/modules/den.nix
···11{ inputs, ... }:
22{
33+ # we can import this flakeModule even if we dont have flake-parts as input!
44+ imports = [ inputs.den.flakeModule ];
55+36 # tux user on igloo host.
47 den.hosts.x86_64-linux.igloo.users.tux = { };
58···811 nixos =
912 { pkgs, ... }:
1013 {
1111- # remove these for a real bootable host
1212- boot.loader.grub.enable = false;
1313- fileSystems."/".device = "/dev/fake";
1414 passthru = { };
1515-1615 environment.systemPackages = [
1716 pkgs.vim
1817 ];