···1+{ inputs, lib, ... }:
2+if inputs ? flake-parts then
3+ { }
4+else
5+ {
6+ # NOTE: Currently Den needs a top-level attribute where to place configurations,
7+ # by default it is the `flake` attribute, even if Den uses no flake-parts at all.
8+ options.flake = lib.mkOption {
9+ type = lib.types.submodule { freeformType = lib.types.anything; };
10+ };
11+ }
···45Create a **minimal** bug reproduction at [`modules/bug.nix`](modules/bug.nix)
6007Then run tests:
89-```console
10nix flake check
11```
1213-Format code with:
0000001415-```console
16-nix fmt
017```
···45Create a **minimal** bug reproduction at [`modules/bug.nix`](modules/bug.nix)
67+See also [Den debugging tips](https://den.oeiuwq.com/debugging.html)
8+9Then run tests:
1011+```shell
12nix flake check
13```
1415+Please share a link to your reproduction repo, showing the CI step and the
16+error at CI build.
17+18+## Fixing Den
19+20+If you are contributing a bug-fix PR, you can use the following command to
21+use your local den checkout.
2223+```shell
24+cd <den-working-copy>
25+nix flake check --override-input den . ./templates/bogus
26```
···1-# DO-NOT-CHANGE. Keep your reproduction minimalistic!
2-#
3-# try not adding new inputs
4-# but if you have no options (pun intended)
5-# here's the place.
6-#
7-# IF you make any change to this file, use:
8-# `nix run .#write-flake`
9-#
10-# We provide nix-darwin and home-manager for common usage.
11-{
12- # change "main" with a commit where bug is present
13- flake-file.inputs.den.url = "github:vic/den/main";
14-15- # included so we can test HM integrations.
16- flake-file.inputs.home-manager = {
17- url = "github:nix-community/home-manager";
18- inputs.nixpkgs.follows = "nixpkgs";
19- };
20-21- # included for testing darwin hosts.
22- flake-file.inputs.darwin = {
23- url = "github:nix-darwin/nix-darwin";
24- inputs.nixpkgs.follows = "nixpkgs";
25- };
26-}
···1+# tests for extending `den.base.*` modules with capabilities (options).
2+# these allow you to expend all hosts/users/homes with custom option
3+# that can later be used by aspects for providing features.
4+#
5+{ lib, ... }:
6+{
7+8+ # This module is base for all host configs.
9+ den.base.host =
10+ { host, ... }:
11+ {
12+ options.capabilities.ssh-server = lib.mkEnableOption "Does host ${host.name} provide ssh?";
13+ };
14+15+ # This module is base for all user configs.
16+ den.base.user =
17+ { user, ... }:
18+ {
19+ options.isAdmin = lib.mkOption {
20+ type = lib.types.bool;
21+ default = user.name == "alice"; # only alice is always admin
22+ };
23+ };
24+25+ # This module is base for all home configs.
26+ # den.base.home = { home, ... }: { };
27+28+ # This one is included on each host/user/home
29+ # it cannot access host/user/home values since this conf is generic.
30+ den.base.conf = {
31+ options.foo = lib.mkOption {
32+ type = lib.types.str;
33+ default = "bar";
34+ };
35+ };
36+37+ # Now hosts and users can set any option defined in base modules.
38+ den.hosts.x86_64-linux.rockhopper = {
39+ capabilities.ssh-server = true;
40+ foo = "boo";
41+ users.alice = {
42+ # isAdmin = true; # alice is admin by default, nothing explicit here.
43+ foo = "moo";
44+ };
45+ };
46+47+ den.aspects.rockhopper.includes =
48+ let
49+ # An aspect can make use of these options to provide configuration.
50+ sshCapable =
51+ { host, ... }:
52+ {
53+ nixos.services.sshd.enable = host.capabilities.ssh-server;
54+ homeManager.services.ssh-agent.enable = host.capabilities.ssh-server;
55+ };
56+ in
57+ [ sshCapable ];
58+59+ # CI checks
60+ perSystem =
61+ {
62+ checkCond,
63+ rockhopper,
64+ alice-at-rockhopper,
65+ ...
66+ }:
67+ {
68+ checks.host-conf-rockhopper-sshd = checkCond "sshd enabled" (
69+ rockhopper.config.services.sshd.enable == true
70+ );
71+ checks.host-conf-alice-sshd = checkCond "ssh-agent enabled" (
72+ alice-at-rockhopper.services.ssh-agent.enable == true
73+ );
74+ };
75+76+}
···1+# User TODO: Remove this file.
2+{
3+ # default aspect can be used for global static settings.
4+ den.default = {
5+ # static values.
6+ darwin.system.stateVersion = 6;
7+ nixos.system.stateVersion = "25.05";
8+ homeManager.home.stateVersion = "25.05";
9+10+ # these defaults are set for checking with CI.
11+ nixos.programs.vim.enable = true;
12+ darwin.programs.zsh.enable = true;
13+ };
14+}
+27
templates/ci/modules/define-user.nix
···000000000000000000000000000
···1+{ den, ... }:
2+{
3+ den.default.includes = [
4+ # Example: parametric over many contexts: { home }, { host, user }, { fromUser, toHost }
5+ den.provides.define-user
6+ ];
7+8+ perSystem =
9+ {
10+ checkCond,
11+ rockhopper,
12+ adelie,
13+ ...
14+ }:
15+ {
16+17+ checks.alice-exists-on-rockhopper = checkCond "den.default.user.includes defines user on host" (
18+ rockhopper.config.users.users.alice.isNormalUser
19+ );
20+ checks.alice-not-exists-on-adelie = checkCond "den.default.user.includes defines user on host" (
21+ !adelie.config.users.users ? alice
22+ );
23+ checks.will-exists-on-adelie = checkCond "den.default.user.includes defines user on host" (
24+ adelie.config.users.users.will.isNormalUser
25+ );
26+ };
27+}
···1+# this is a non-dendritic darwin class module file.
2+# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
3+{ ... }:
4+{
5+ # see nix-darwin options.
6+}
···1+# this is a non-dendritic nix class module file.
2+# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
3+#
4+# USER TODO: Remove this file.
5+# suppose this file was auto-generated by nixos-generate-config or some other hardware tooling.
6+{ lib, ... }:
7+{
8+ # used in CI to test this file was actually imported.
9+ options.auto-imported = lib.mkOption {
10+ readOnly = true;
11+ type = lib.types.bool;
12+ default = true;
13+ };
14+}
···16nix flake check
17```
1819-- Read [modules/den.nix](modules/den.nix) where hosts and homes definitions are for this example.
20-21-- Read [modules/namespace.nix](modules/namespace.nix) where a new `eg` (an example) aspects namespace is created.
22-23-- Read [modules/aspects/igloo.nix](modules/aspects/igloo.nix) where the `igloo` host is configured.
24-25-- Read [modules/aspects/alice.nix](modules/aspects/alice.nix) where the `alice` user is configured.
26-27-- Run the VM.
28-29-```console
30-nix run .#vm
31-```
32-33-- Edit and run VM loop.
34-35-Feel free to add more aspects, organize things to your liking.
···1-{ den, eg, ... }:
2-{
3- den.aspects.alice = {
4-5- # Alice can include other aspects.
6- # For small, private one-shot aspects, use let-bindings like here.
7- # for more complex or re-usable ones, define on their own modules,
8- # as part of any aspect-subtree.
9- includes =
10- let
11- # hack for nixf linter to keep findFile :/
12- unused = den.lib.take.unused __findFile;
13- __findFile = unused den.lib.__findFile;
14-15- customEmacs.homeManager =
16- { pkgs, ... }:
17- {
18- programs.emacs.enable = true;
19- programs.emacs.package = pkgs.emacs30-nox;
20- };
21- in
22- [
23- # from local bindings.
24- customEmacs
25- # from the aspect tree, cooper example is defined bellow
26- den.aspects.cooper
27- den.aspects.setHost
28- # from the `eg` namespace.
29- eg.autologin
30- # den included batteries that provide common configs.
31- <den/primary-user> # alice is admin always.
32- (<den/user-shell> "fish") # default user shell
33- ];
34-35- # Alice configures NixOS hosts it lives on.
36- nixos =
37- { pkgs, ... }:
38- {
39- users.users.alice.packages = [ pkgs.vim ];
40- };
41-42- # Alice home-manager.
43- homeManager =
44- { pkgs, ... }:
45- {
46- home.packages = [ pkgs.htop ];
47- };
48-49- # <user>.provides.<host>, via eg/routes.nix
50- provides.igloo =
51- { host, ... }:
52- {
53- nixos.programs.nh.enable = host.name == "igloo";
54- };
55- };
56-57- # This is a context-aware aspect, that emits configurations
58- # **anytime** at least the `user` data is in context.
59- # read more at https://vic.github.io/den/context-aware.html
60- den.aspects.cooper =
61- { user, ... }:
62- {
63- nixos.users.users.${user.userName}.description = "Alice Cooper";
64- };
65-66- den.aspects.setHost =
67- { host, ... }:
68- {
69- networking.hostName = host.hostName;
70- };
71-}
···1-{
2- config,
3- # deadnix: skip # enable <den/brackets> syntax for demo.
4- __findFile ? __findFile,
5- den,
6- ...
7-}:
8-{
9- # Lets also configure some defaults using aspects.
10- # These are global static settings.
11- den.default = {
12- darwin.system.stateVersion = 6;
13- nixos.system.stateVersion = "25.05";
14- homeManager.home.stateVersion = "25.05";
15- };
16-17- # These are functions that produce configs
18- den.default.includes = [
19- # ${user}.provides.${host} and ${host}.provides.${user}
20- <eg/routes>
21-22- # Enable home-manager on all hosts.
23- <den/home-manager>
24-25- # Automatically create the user on host.
26- <den/define-user>
27-28- # Disable booting when running on CI on all NixOS hosts.
29- (if config ? _module.args.CI then <eg/ci-no-boot> else { })
30-31- # NOTE: be cautious when adding fully parametric functions to defaults.
32- # defaults are included on EVERY host/user/home, and IF you are not careful
33- # you could be duplicating config values. For example:
34- #
35- # # This will append 42 into foo option for the {host} and for EVERY {host,user}
36- # ({ host, ... }: { nixos.foo = [ 42 ]; }) # DO-NOT-DO-THIS.
37- #
38- # # Instead try to be explicit if a function is intended for ONLY { host }.
39- (den.lib.take.exactly (
40- { OS, host }:
41- den.lib.take.unused OS {
42- nixos.networking.hostName = host.hostName;
43- }
44- ))
45-46- ];
47-}
···1-{
2- # autologin is context-aware, parametric aspect.
3- # it applies only if the context has at least { user }
4- # meaning that has access to user data
5- eg.autologin =
6- { user, ... }:
7- {
8- nixos =
9- { config, lib, ... }:
10- lib.mkIf config.services.displayManager.enable {
11- services.displayManager.autoLogin.enable = true;
12- services.displayManager.autoLogin.user = user.userName;
13- };
14- };
15-}
···1-# This example implements an aspect "routing" pattern.
2-#
3-# Unlike `den.default` which is `parametric.atLeast`
4-# we use `parametric.fixedTo` here, which help us
5-# propagate an already computed context to all includes.
6-#
7-# This aspect, when installed in a `parametric.atLeast`
8-# will just forward the same context.
9-# The `mutual` helper returns an static configuration which
10-# is ignored by parametric aspects, thus allowing
11-# non-existing aspects to be just ignored.
12-#
13-# Be sure to read: https://vic.github.io/den/dependencies.html
14-# See usage at: defaults.nix, alice.nix, igloo.nix
15-#
16-{ den, ... }:
17-{
18- # Usage: `den.default.includes [ eg.routes ]`
19- eg.routes =
20- let
21- inherit (den.lib) parametric;
22-23- # eg, `<user>._.<host>` and `<host>._.<user>`
24- mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { };
25-26- routes =
27- { host, user, ... }@ctx:
28- parametric.fixedTo ctx {
29- includes = [
30- (mutual user host)
31- (mutual host user)
32- ];
33- };
34- in
35- routes;
36-}
···1+{ inputs, ... }:
2{
003 imports = [
4 (inputs.flake-file.flakeModules.dendritic or { })
5 (inputs.den.flakeModules.dendritic or { })
6 ];
7+8+ # other inputs may be defined at a module using them.
9+ flake-file.inputs = {
10+ den.url = "github:vic/den";
11+ flake-file.url = "github:vic/flake-file";
12+ home-manager = {
13+ url = "github:nix-community/home-manager";
14+ inputs.nixpkgs.follows = "nixpkgs";
15+ };
16+ };
17}
+19
templates/default/modules/hosts.nix
···0000000000000000000
···1+# defines all hosts + users + homes.
2+# then config their aspects in as many files you want
3+{
4+ # tux user at igloo host.
5+ den.hosts.x86_64-linux.igloo.users.tux = { };
6+7+ # define an standalone home-manager for tux
8+ # den.homes.x86_64-linux.tux = { };
9+10+ # be sure to add nix-darwin input for this:
11+ # den.hosts.aarch64-darwin.apple.users.alice = { };
12+13+ # other hosts can also have user tux.
14+ # den.hosts.x86_64-linux.south = {
15+ # wsl = { }; # add nixos-wsl input for this.
16+ # users.tux = { };
17+ # users.orca = { };
18+ # };
19+}
···1-# This repo was generated with github:vic/flake-file#dendritic template.
2-# Run `nix run .#write-flake` after changing any input.
3-#
4-# Inputs can be placed in any module, the best practice is to have them
5-# as close as possible to their actual usage.
6-# See: https://vic.github.io/dendrix/Dendritic.html#minimal-and-focused-flakenix
7-#
8-# For our template, we enable home-manager and nix-darwin by default, but
9-# you are free to remove them if not being used by you.
10-{ ... }:
11-{
12-13- flake-file.inputs = {
14- home-manager = {
15- url = "github:nix-community/home-manager";
16- inputs.nixpkgs.follows = "nixpkgs";
17- };
18-19- darwin = {
20- url = "github:nix-darwin/nix-darwin";
21- inputs.nixpkgs.follows = "nixpkgs";
22- };
23-24- ## these stable inputs are for wsl
25- #nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
26- #home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
27- #home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
28-29- #nixos-wsl = {
30- # url = "github:nix-community/nixos-wsl";
31- # inputs.nixpkgs.follows = "nixpkgs-stable";
32- # inputs.flake-compat.follows = "";
33- #};
34-35- };
36-37-}
···0000000000000000000000000000000000000
-15
templates/default/modules/namespace.nix
···1-{ inputs, den, ... }:
2-{
3- # create an `eg` (example!) namespace. (flake exposed)
4- imports = [ (inputs.den.namespace "eg" true) ];
5-6- # you can have more than one namespace (false = not flake exposed)
7- # imports = [ (inputs.den.namespace "my" false) ];
8-9- # you can also merge many namespaces from remote flakes.
10- # keep in mind a namespace is defined only once, so give it an array:
11- # imports = [ (inputs.den.namespace "ours" [inputs.ours inputs.theirs]) ];
12-13- # this line enables den angle brackets syntax in modules.
14- _module.args.__findFile = den.lib.__findFile;
15-}
···1+{ den, ... }:
2+{
3+ # user aspect
4+ den.aspects.tux = {
5+ includes = [
6+ den.provides.primary-user
7+ (den.provides.user-shell "fish")
8+ ];
9+10+ homeManager =
11+ { pkgs, ... }:
12+ {
13+ home.packages = [ pkgs.htop ];
14+ };
15+16+ # user can provide NixOS configurations
17+ # to any host it is included on
18+ # nixos = { pkgs, ... }: { };
19+ };
20+}
-22
templates/default/modules/vm.nix
···1-# enables `nix run .#vm`. it is very useful to have a VM
2-# you can edit your config and launch the VM to test stuff
3-# instead of having to reboot each time.
4-{ inputs, eg, ... }:
5-{
6-7- den.aspects.igloo.includes = [
8- eg.vm._.gui
9- # eg.vm._.tui
10- ];
11-12- perSystem =
13- { pkgs, ... }:
14- {
15- packages.vm = pkgs.writeShellApplication {
16- name = "vm";
17- text = ''
18- ${inputs.self.nixosConfigurations.igloo.config.system.build.vm}/bin/run-igloo-vm "$@"
19- '';
20- };
21- };
22-}
···1+# Examples
2+3+This template provides some basic examples of how to use Den features.
4+However, you will learn more by reading templates/ci which tests all of Den.
5+6+Steps you can follow after cloning this template:
7+8+- Be sure to read the [den documentation](https://vic.github.io/den)
9+10+- Update den input.
11+12+```console
13+nix flake update den
14+```
15+16+- Run checks to test everything works.
17+18+```console
19+nix flake check
20+```
21+22+- Read [modules/den.nix](modules/den.nix) where hosts and homes definitions are for this example.
23+24+- Read [modules/namespace.nix](modules/namespace.nix) where a new `eg` (an example) aspects namespace is created.
25+26+- Read [modules/aspects/igloo.nix](modules/aspects/igloo.nix) where the `igloo` host is configured.
27+28+- Read [modules/aspects/alice.nix](modules/aspects/alice.nix) where the `alice` user is configured.
29+30+- Run the VM.
31+32+```console
33+nix run .#vm
34+```
35+36+- Edit and run VM loop.
37+38+Feel free to add more aspects, organize things to your liking.
···1+{ den, eg, ... }:
2+{
3+ den.aspects.alice = {
4+5+ # Alice can include other aspects.
6+ # For small, private one-shot aspects, use let-bindings like here.
7+ # for more complex or re-usable ones, define on their own modules,
8+ # as part of any aspect-subtree.
9+ includes =
10+ let
11+ # hack for nixf linter to keep findFile :/
12+ unused = den.lib.take.unused __findFile;
13+ __findFile = unused den.lib.__findFile;
14+15+ customEmacs.homeManager =
16+ { pkgs, ... }:
17+ {
18+ programs.emacs.enable = true;
19+ programs.emacs.package = pkgs.emacs30-nox;
20+ };
21+ in
22+ [
23+ # from local bindings.
24+ customEmacs
25+ # from the aspect tree, cooper example is defined bellow
26+ den.aspects.cooper
27+ den.aspects.setHost
28+ # from the `eg` namespace.
29+ eg.autologin
30+ # den included batteries that provide common configs.
31+ <den/primary-user> # alice is admin always.
32+ (<den/user-shell> "fish") # default user shell
33+ ];
34+35+ # Alice configures NixOS hosts it lives on.
36+ nixos =
37+ { pkgs, ... }:
38+ {
39+ users.users.alice.packages = [ pkgs.vim ];
40+ };
41+42+ # Alice home-manager.
43+ homeManager =
44+ { pkgs, ... }:
45+ {
46+ home.packages = [ pkgs.htop ];
47+ };
48+49+ # <user>.provides.<host>, via eg/routes.nix
50+ provides.igloo =
51+ { host, ... }:
52+ {
53+ nixos.programs.nh.enable = host.name == "igloo";
54+ };
55+ };
56+57+ # This is a context-aware aspect, that emits configurations
58+ # **anytime** at least the `user` data is in context.
59+ # read more at https://vic.github.io/den/context-aware.html
60+ den.aspects.cooper =
61+ { user, ... }:
62+ {
63+ nixos.users.users.${user.userName}.description = "Alice Cooper";
64+ };
65+66+ den.aspects.setHost =
67+ { host, ... }:
68+ {
69+ networking.hostName = host.hostName;
70+ };
71+}
···1+{
2+ config,
3+ # deadnix: skip # enable <den/brackets> syntax for demo.
4+ __findFile ? __findFile,
5+ den,
6+ ...
7+}:
8+{
9+ # Lets also configure some defaults using aspects.
10+ # These are global static settings.
11+ den.default = {
12+ darwin.system.stateVersion = 6;
13+ nixos.system.stateVersion = "25.05";
14+ homeManager.home.stateVersion = "25.05";
15+ };
16+17+ # These are functions that produce configs
18+ den.default.includes = [
19+ # ${user}.provides.${host} and ${host}.provides.${user}
20+ <eg/routes>
21+22+ # Enable home-manager on all hosts.
23+ <den/home-manager>
24+25+ # Automatically create the user on host.
26+ <den/define-user>
27+28+ # Disable booting when running on CI on all NixOS hosts.
29+ (if config ? _module.args.CI then <eg/ci-no-boot> else { })
30+31+ # NOTE: be cautious when adding fully parametric functions to defaults.
32+ # defaults are included on EVERY host/user/home, and IF you are not careful
33+ # you could be duplicating config values. For example:
34+ #
35+ # # This will append 42 into foo option for the {host} and for EVERY {host,user}
36+ # ({ host, ... }: { nixos.foo = [ 42 ]; }) # DO-NOT-DO-THIS.
37+ #
38+ # # Instead try to be explicit if a function is intended for ONLY { host }.
39+ (den.lib.take.exactly (
40+ { OS, host }:
41+ den.lib.take.unused OS {
42+ nixos.networking.hostName = host.hostName;
43+ }
44+ ))
45+46+ ];
47+}
···1+{
2+ # autologin is context-aware, parametric aspect.
3+ # it applies only if the context has at least { user }
4+ # meaning that has access to user data
5+ eg.autologin =
6+ { user, ... }:
7+ {
8+ nixos =
9+ { config, lib, ... }:
10+ lib.mkIf config.services.displayManager.enable {
11+ services.displayManager.autoLogin.enable = true;
12+ services.displayManager.autoLogin.user = user.userName;
13+ };
14+ };
15+}
···1+# This example implements an aspect "routing" pattern.
2+#
3+# Unlike `den.default` which is `parametric.atLeast`
4+# we use `parametric.fixedTo` here, which help us
5+# propagate an already computed context to all includes.
6+#
7+# This aspect, when installed in a `parametric.atLeast`
8+# will just forward the same context.
9+# The `mutual` helper returns an static configuration which
10+# is ignored by parametric aspects, thus allowing
11+# non-existing aspects to be just ignored.
12+#
13+# Be sure to read: https://vic.github.io/den/dependencies.html
14+# See usage at: defaults.nix, alice.nix, igloo.nix
15+#
16+{ den, ... }:
17+{
18+ # Usage: `den.default.includes [ eg.routes ]`
19+ eg.routes =
20+ let
21+ inherit (den.lib) parametric;
22+23+ # eg, `<user>._.<host>` and `<host>._.<user>`
24+ mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { };
25+26+ routes =
27+ { host, user, ... }@ctx:
28+ parametric.fixedTo ctx {
29+ includes = [
30+ (mutual user host)
31+ (mutual host user)
32+ ];
33+ };
34+ in
35+ routes;
36+}
···1+# This repo was generated with github:vic/flake-file#dendritic template.
2+# Run `nix run .#write-flake` after changing any input.
3+#
4+# Inputs can be placed in any module, the best practice is to have them
5+# as close as possible to their actual usage.
6+# See: https://vic.github.io/dendrix/Dendritic.html#minimal-and-focused-flakenix
7+#
8+# For our template, we enable home-manager and nix-darwin by default, but
9+# you are free to remove them if not being used by you.
10+{ ... }:
11+{
12+13+ flake-file.inputs = {
14+ home-manager = {
15+ url = "github:nix-community/home-manager";
16+ inputs.nixpkgs.follows = "nixpkgs";
17+ };
18+19+ darwin = {
20+ url = "github:nix-darwin/nix-darwin";
21+ inputs.nixpkgs.follows = "nixpkgs";
22+ };
23+24+ ## these stable inputs are for wsl
25+ #nixpkgs-stable.url = "github:nixos/nixpkgs/release-25.05";
26+ #home-manager-stable.url = "github:nix-community/home-manager/release-25.05";
27+ #home-manager-stable.inputs.nixpkgs.follows = "nixpkgs-stable";
28+29+ #nixos-wsl = {
30+ # url = "github:nix-community/nixos-wsl";
31+ # inputs.nixpkgs.follows = "nixpkgs-stable";
32+ # inputs.flake-compat.follows = "";
33+ #};
34+35+ };
36+37+}
+15
templates/example/modules/namespace.nix
···000000000000000
···1+{ inputs, den, ... }:
2+{
3+ # create an `eg` (example!) namespace. (flake exposed)
4+ imports = [ (inputs.den.namespace "eg" true) ];
5+6+ # you can have more than one namespace (false = not flake exposed)
7+ # imports = [ (inputs.den.namespace "my" false) ];
8+9+ # you can also merge many namespaces from remote flakes.
10+ # keep in mind a namespace is defined only once, so give it an array:
11+ # imports = [ (inputs.den.namespace "ours" [inputs.ours inputs.theirs]) ];
12+13+ # this line enables den angle brackets syntax in modules.
14+ _module.args.__findFile = den.lib.__findFile;
15+}
···1+# enables `nix run .#vm`. it is very useful to have a VM
2+# you can edit your config and launch the VM to test stuff
3+# instead of having to reboot each time.
4+{ inputs, eg, ... }:
5+{
6+7+ den.aspects.igloo.includes = [
8+ eg.vm._.gui
9+ # eg.vm._.tui
10+ ];
11+12+ perSystem =
13+ { pkgs, ... }:
14+ {
15+ packages.vm = pkgs.writeShellApplication {
16+ name = "vm";
17+ text = ''
18+ ${inputs.self.nixosConfigurations.igloo.config.system.build.vm}/bin/run-igloo-vm "$@"
19+ '';
20+ };
21+ };
22+}
···1-User TODO: REMOVE this directory (or disable its import from den.nix)
2-3-It is used to implement tests for all den features so we can validate at CI.
4-5-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.
···1-# this is a non-dendritic darwin class module file.
2-# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
3-{ ... }:
4-{
5- # see nix-darwin options.
6-}
···1-# this is a non-dendritic nix class module file.
2-# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
3-#
4-# USER TODO: Remove this file.
5-# suppose this file was auto-generated by nixos-generate-config or some other hardware tooling.
6-{ lib, ... }:
7-{
8- # used in CI to test this file was actually imported.
9- options.auto-imported = lib.mkOption {
10- readOnly = true;
11- type = lib.types.bool;
12- default = true;
13- };
14-}
···1-# tests for extending `den.base.*` modules with capabilities (options).
2-# these allow you to expend all hosts/users/homes with custom option
3-# that can later be used by aspects for providing features.
4-#
5-{ lib, ... }:
6-{
7-8- # This module is base for all host configs.
9- den.base.host =
10- { host, ... }:
11- {
12- options.capabilities.ssh-server = lib.mkEnableOption "Does host ${host.name} provide ssh?";
13- };
14-15- # This module is base for all user configs.
16- den.base.user =
17- { user, ... }:
18- {
19- options.isAdmin = lib.mkOption {
20- type = lib.types.bool;
21- default = user.name == "alice"; # only alice is always admin
22- };
23- };
24-25- # This module is base for all home configs.
26- # den.base.home = { home, ... }: { };
27-28- # This one is included on each host/user/home
29- # it cannot access host/user/home values since this conf is generic.
30- den.base.conf = {
31- options.foo = lib.mkOption {
32- type = lib.types.str;
33- default = "bar";
34- };
35- };
36-37- # Now hosts and users can set any option defined in base modules.
38- den.hosts.x86_64-linux.rockhopper = {
39- capabilities.ssh-server = true;
40- foo = "boo";
41- users.alice = {
42- # isAdmin = true; # alice is admin by default, nothing explicit here.
43- foo = "moo";
44- };
45- };
46-47- den.aspects.rockhopper.includes =
48- let
49- # An aspect can make use of these options to provide configuration.
50- sshCapable =
51- { host, ... }:
52- {
53- nixos.services.sshd.enable = host.capabilities.ssh-server;
54- homeManager.services.ssh-agent.enable = host.capabilities.ssh-server;
55- };
56- in
57- [ sshCapable ];
58-59- # CI checks
60- perSystem =
61- {
62- checkCond,
63- rockhopper,
64- alice-at-rockhopper,
65- ...
66- }:
67- {
68- checks.host-conf-rockhopper-sshd = checkCond "sshd enabled" (
69- rockhopper.config.services.sshd.enable == true
70- );
71- checks.host-conf-alice-sshd = checkCond "ssh-agent enabled" (
72- alice-at-rockhopper.services.ssh-agent.enable == true
73- );
74- };
75-76-}
···1-# User TODO: Remove this file.
2-{
3- # default aspect can be used for global static settings.
4- den.default = {
5- # static values.
6- darwin.system.stateVersion = 6;
7- nixos.system.stateVersion = "25.05";
8- homeManager.home.stateVersion = "25.05";
9-10- # these defaults are set for checking with CI.
11- nixos.programs.vim.enable = true;
12- darwin.programs.zsh.enable = true;
13- };
14-}
···1-# USER TODO: remove this file.
2-# copy any desired module to your ./modules and let it be auto-imported.
3-{ inputs, ... }:
4-{
5- imports = [
6- # The _example directory contains CI tests for all den features.
7- # use it as reference of usage, but not of best practices.
8- (inputs.import-tree ./_example)
9- ];
10-}
···1{ inputs, den, ... }:
2{
3+ # we can import this flakeModule even if we dont have flake-parts as input!
4 imports = [ inputs.den.flakeModule ];
56 den.hosts.x86_64-linux.igloo.users.tux = { };
7+8+ den.aspects.igloo = {
9+ nixos =
10+ { pkgs, ... }:
11+ {
12+ environment.systemPackages = [ pkgs.hello ];
13+ passthru = { };
14+ };
15+ };
16+17+ den.aspects.tux = {
18+ includes = [ den.provides.primary-user ];
19+ };
20}
+17
templates/noflake/README.md
···00000000000000000
···1+# Den without flake-parts
2+3+This template provides an example Den setup with stable Nix (no-flakes), no flake-parts and nix-maid instead of home-manager.
4+5+It configures a NixOS host with one user.
6+7+Try it with:
8+9+```shell
10+nixos-rebuild build --file . -A nixosConfigurations.igloo
11+```
12+13+or
14+15+```shell
16+nix-build -A nixosConfigurations.igloo.config.system.build.toplevel
17+```
+16
templates/noflake/default.nix
···0000000000000000
···1+let
2+3+ outputs =
4+ inputs:
5+ (inputs.nixpkgs.lib.evalModules {
6+ modules = [ (inputs.import-tree ./modules) ];
7+ specialArgs = {
8+ inherit inputs;
9+ inherit (inputs) self;
10+ };
11+ }).config;
12+13+in
14+# Den outputs configurations at flake top-level attr
15+# even when it does not depend on flakes or flake-parts.
16+(import ./with-inputs.nix outputs).flake
+37
templates/noflake/modules/den.nix
···0000000000000000000000000000000000000
···1+{ inputs, ... }:
2+{
3+ # we can import this flakeModule even if we dont have flake-parts as input!
4+ imports = [ inputs.den.flakeModule ];
5+6+ # tux user on igloo host.
7+ den.hosts.x86_64-linux.igloo.users.tux = { };
8+9+ # host aspect
10+ den.aspects.igloo = {
11+ nixos =
12+ { pkgs, ... }:
13+ {
14+ passthru = { };
15+ environment.systemPackages = [
16+ pkgs.vim
17+ ];
18+ };
19+ };
20+21+ # user aspect
22+ den.aspects.tux = {
23+ # user configures the host it lives in
24+ nixos = {
25+ # hipster-tux uses nix-maid instead of home-manager.
26+ imports = [ inputs.nix-maid.nixosModules.default ];
27+28+ users.users.tux = {
29+ isNormalUser = true;
30+ maid.file.home.".gitconfig".text = ''
31+ [user]
32+ name=Tux
33+ '';
34+ };
35+ };
36+ };
37+}
···1+/*
2+ This file is provided under the MIT licence:
3+4+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the โSoftwareโ), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5+6+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7+8+ THE SOFTWARE IS PROVIDED โAS ISโ, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9+*/
10+# Generated by npins. Do not modify; will be overwritten regularly
11+let
12+ # Backwards-compatibly make something that previously didn't take any arguments take some
13+ # The function must return an attrset, and will unfortunately be eagerly evaluated
14+ # Same thing, but it catches eval errors on the default argument so that one may still call it with other arguments
15+ mkFunctor =
16+ fn:
17+ let
18+ e = builtins.tryEval (fn { });
19+ in
20+ (if e.success then e.value else { error = fn { }; }) // { __functor = _self: fn; };
21+22+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
23+ range =
24+ first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
25+26+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
27+ stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
28+29+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
30+ stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
31+ concatStrings = builtins.concatStringsSep "";
32+33+ # If the environment variable NPINS_OVERRIDE_${name} is set, then use
34+ # the path directly as opposed to the fetched source.
35+ # (Taken from Niv for compatibility)
36+ mayOverride =
37+ name: path:
38+ let
39+ envVarName = "NPINS_OVERRIDE_${saneName}";
40+ saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
41+ ersatz = builtins.getEnv envVarName;
42+ in
43+ if ersatz == "" then
44+ path
45+ else
46+ # this turns the string into an actual Nix path (for both absolute and
47+ # relative paths)
48+ builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
49+ if builtins.substring 0 1 ersatz == "/" then
50+ /. + ersatz
51+ else
52+ /. + builtins.getEnv "PWD" + "/${ersatz}"
53+ );
54+55+ mkSource =
56+ name: spec:
57+ {
58+ pkgs ? null,
59+ }:
60+ assert spec ? type;
61+ let
62+ # Unify across builtin and pkgs fetchers.
63+ # `fetchGit` requires a wrapper because of slight API differences.
64+ fetchers =
65+ if pkgs == null then
66+ {
67+ inherit (builtins) fetchTarball fetchurl;
68+ # For some fucking reason, fetchGit has a different signature than the other builtin fetchers โฆ
69+ fetchGit = args: (builtins.fetchGit args).outPath;
70+ }
71+ else
72+ {
73+ fetchTarball =
74+ {
75+ url,
76+ sha256,
77+ }:
78+ pkgs.fetchzip {
79+ inherit url sha256;
80+ extension = "tar";
81+ };
82+ inherit (pkgs) fetchurl;
83+ fetchGit =
84+ {
85+ url,
86+ submodules,
87+ rev,
88+ name,
89+ narHash,
90+ }:
91+ pkgs.fetchgit {
92+ inherit url rev name;
93+ fetchSubmodules = submodules;
94+ hash = narHash;
95+ };
96+ };
97+98+ # Dispatch to the correct code path based on the type
99+ path =
100+ if spec.type == "Git" then
101+ mkGitSource fetchers spec
102+ else if spec.type == "GitRelease" then
103+ mkGitSource fetchers spec
104+ else if spec.type == "PyPi" then
105+ mkPyPiSource fetchers spec
106+ else if spec.type == "Channel" then
107+ mkChannelSource fetchers spec
108+ else if spec.type == "Tarball" then
109+ mkTarballSource fetchers spec
110+ else if spec.type == "Container" then
111+ mkContainerSource pkgs spec
112+ else
113+ builtins.throw "Unknown source type ${spec.type}";
114+ in
115+ spec // { outPath = mayOverride name path; };
116+117+ mkGitSource =
118+ {
119+ fetchTarball,
120+ fetchGit,
121+ ...
122+ }:
123+ {
124+ repository,
125+ revision,
126+ url ? null,
127+ submodules,
128+ hash,
129+ ...
130+ }:
131+ assert repository ? type;
132+ # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
133+ # In the latter case, there we will always be an url to the tarball
134+ if url != null && !submodules then
135+ fetchTarball {
136+ inherit url;
137+ sha256 = hash;
138+ }
139+ else
140+ let
141+ url =
142+ if repository.type == "Git" then
143+ repository.url
144+ else if repository.type == "GitHub" then
145+ "https://github.com/${repository.owner}/${repository.repo}.git"
146+ else if repository.type == "GitLab" then
147+ "${repository.server}/${repository.repo_path}.git"
148+ else if repository.type == "Forgejo" then
149+ "${repository.server}/${repository.owner}/${repository.repo}.git"
150+ else
151+ throw "Unrecognized repository type ${repository.type}";
152+ urlToName =
153+ url: rev:
154+ let
155+ matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
156+157+ short = builtins.substring 0 7 rev;
158+159+ appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
160+ in
161+ "${if matched == null then "source" else builtins.head matched}${appendShort}";
162+ name = urlToName url revision;
163+ in
164+ fetchGit {
165+ rev = revision;
166+ narHash = hash;
167+168+ inherit name submodules url;
169+ };
170+171+ mkPyPiSource =
172+ { fetchurl, ... }:
173+ {
174+ url,
175+ hash,
176+ ...
177+ }:
178+ fetchurl {
179+ inherit url;
180+ sha256 = hash;
181+ };
182+183+ mkChannelSource =
184+ { fetchTarball, ... }:
185+ {
186+ url,
187+ hash,
188+ ...
189+ }:
190+ fetchTarball {
191+ inherit url;
192+ sha256 = hash;
193+ };
194+195+ mkTarballSource =
196+ { fetchTarball, ... }:
197+ {
198+ url,
199+ locked_url ? url,
200+ hash,
201+ ...
202+ }:
203+ fetchTarball {
204+ url = locked_url;
205+ sha256 = hash;
206+ };
207+208+ mkContainerSource =
209+ pkgs:
210+ {
211+ image_name,
212+ image_tag,
213+ image_digest,
214+ ...
215+ }:
216+ if pkgs == null then
217+ builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers"
218+ else
219+ pkgs.dockerTools.pullImage {
220+ imageName = image_name;
221+ imageDigest = image_digest;
222+ finalImageTag = image_tag;
223+ };
224+in
225+mkFunctor (
226+ {
227+ input ? ./sources.json,
228+ }:
229+ let
230+ data =
231+ if builtins.isPath input then
232+ # while `readFile` will throw an error anyways if the path doesn't exist,
233+ # we still need to check beforehand because *our* error can be caught but not the one from the builtin
234+ # *piegames sighs*
235+ if builtins.pathExists input then
236+ builtins.fromJSON (builtins.readFile input)
237+ else
238+ throw "Input path ${toString input} does not exist"
239+ else if builtins.isAttrs input then
240+ input
241+ else
242+ throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset";
243+ version = data.version;
244+ in
245+ if version == 7 then
246+ builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins
247+ else
248+ throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
249+)