···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}
+17
templates/noflake/README.md
···11+# Den without flake-parts
22+33+This template provides an example Den setup with stable Nix (no-flakes), no flake-parts and nix-maid instead of home-manager.
44+55+It configures a NixOS host with one user.
66+77+Try it with:
88+99+```shell
1010+nixos-rebuild build --file . -A nixosConfigurations.igloo
1111+```
1212+1313+or
1414+1515+```shell
1616+nix-build -A nixosConfigurations.igloo.config.system.build.toplevel
1717+```
+16
templates/noflake/default.nix
···11+let
22+33+ outputs =
44+ inputs:
55+ (inputs.nixpkgs.lib.evalModules {
66+ modules = [ (inputs.import-tree ./modules) ];
77+ specialArgs = {
88+ inherit inputs;
99+ inherit (inputs) self;
1010+ };
1111+ }).config;
1212+1313+in
1414+# Den outputs configurations at flake top-level attr
1515+# even when it does not depend on flakes or flake-parts.
1616+(import ./with-inputs.nix outputs).flake
+37
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+66+ # tux user on igloo host.
77+ den.hosts.x86_64-linux.igloo.users.tux = { };
88+99+ # host aspect
1010+ den.aspects.igloo = {
1111+ nixos =
1212+ { pkgs, ... }:
1313+ {
1414+ passthru = { };
1515+ environment.systemPackages = [
1616+ pkgs.vim
1717+ ];
1818+ };
1919+ };
2020+2121+ # user aspect
2222+ den.aspects.tux = {
2323+ # user configures the host it lives in
2424+ nixos = {
2525+ # hipster-tux uses nix-maid instead of home-manager.
2626+ imports = [ inputs.nix-maid.nixosModules.default ];
2727+2828+ users.users.tux = {
2929+ isNormalUser = true;
3030+ maid.file.home.".gitconfig".text = ''
3131+ [user]
3232+ name=Tux
3333+ '';
3434+ };
3535+ };
3636+ };
3737+}
+249
templates/noflake/npins/default.nix
···11+/*
22+ This file is provided under the MIT licence:
33+44+ 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:
55+66+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
77+88+ 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.
99+*/
1010+# Generated by npins. Do not modify; will be overwritten regularly
1111+let
1212+ # Backwards-compatibly make something that previously didn't take any arguments take some
1313+ # The function must return an attrset, and will unfortunately be eagerly evaluated
1414+ # Same thing, but it catches eval errors on the default argument so that one may still call it with other arguments
1515+ mkFunctor =
1616+ fn:
1717+ let
1818+ e = builtins.tryEval (fn { });
1919+ in
2020+ (if e.success then e.value else { error = fn { }; }) // { __functor = _self: fn; };
2121+2222+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
2323+ range =
2424+ first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1);
2525+2626+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
2727+ stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
2828+2929+ # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
3030+ stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
3131+ concatStrings = builtins.concatStringsSep "";
3232+3333+ # If the environment variable NPINS_OVERRIDE_${name} is set, then use
3434+ # the path directly as opposed to the fetched source.
3535+ # (Taken from Niv for compatibility)
3636+ mayOverride =
3737+ name: path:
3838+ let
3939+ envVarName = "NPINS_OVERRIDE_${saneName}";
4040+ saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name;
4141+ ersatz = builtins.getEnv envVarName;
4242+ in
4343+ if ersatz == "" then
4444+ path
4545+ else
4646+ # this turns the string into an actual Nix path (for both absolute and
4747+ # relative paths)
4848+ builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" (
4949+ if builtins.substring 0 1 ersatz == "/" then
5050+ /. + ersatz
5151+ else
5252+ /. + builtins.getEnv "PWD" + "/${ersatz}"
5353+ );
5454+5555+ mkSource =
5656+ name: spec:
5757+ {
5858+ pkgs ? null,
5959+ }:
6060+ assert spec ? type;
6161+ let
6262+ # Unify across builtin and pkgs fetchers.
6363+ # `fetchGit` requires a wrapper because of slight API differences.
6464+ fetchers =
6565+ if pkgs == null then
6666+ {
6767+ inherit (builtins) fetchTarball fetchurl;
6868+ # For some fucking reason, fetchGit has a different signature than the other builtin fetchers โฆ
6969+ fetchGit = args: (builtins.fetchGit args).outPath;
7070+ }
7171+ else
7272+ {
7373+ fetchTarball =
7474+ {
7575+ url,
7676+ sha256,
7777+ }:
7878+ pkgs.fetchzip {
7979+ inherit url sha256;
8080+ extension = "tar";
8181+ };
8282+ inherit (pkgs) fetchurl;
8383+ fetchGit =
8484+ {
8585+ url,
8686+ submodules,
8787+ rev,
8888+ name,
8989+ narHash,
9090+ }:
9191+ pkgs.fetchgit {
9292+ inherit url rev name;
9393+ fetchSubmodules = submodules;
9494+ hash = narHash;
9595+ };
9696+ };
9797+9898+ # Dispatch to the correct code path based on the type
9999+ path =
100100+ if spec.type == "Git" then
101101+ mkGitSource fetchers spec
102102+ else if spec.type == "GitRelease" then
103103+ mkGitSource fetchers spec
104104+ else if spec.type == "PyPi" then
105105+ mkPyPiSource fetchers spec
106106+ else if spec.type == "Channel" then
107107+ mkChannelSource fetchers spec
108108+ else if spec.type == "Tarball" then
109109+ mkTarballSource fetchers spec
110110+ else if spec.type == "Container" then
111111+ mkContainerSource pkgs spec
112112+ else
113113+ builtins.throw "Unknown source type ${spec.type}";
114114+ in
115115+ spec // { outPath = mayOverride name path; };
116116+117117+ mkGitSource =
118118+ {
119119+ fetchTarball,
120120+ fetchGit,
121121+ ...
122122+ }:
123123+ {
124124+ repository,
125125+ revision,
126126+ url ? null,
127127+ submodules,
128128+ hash,
129129+ ...
130130+ }:
131131+ assert repository ? type;
132132+ # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
133133+ # In the latter case, there we will always be an url to the tarball
134134+ if url != null && !submodules then
135135+ fetchTarball {
136136+ inherit url;
137137+ sha256 = hash;
138138+ }
139139+ else
140140+ let
141141+ url =
142142+ if repository.type == "Git" then
143143+ repository.url
144144+ else if repository.type == "GitHub" then
145145+ "https://github.com/${repository.owner}/${repository.repo}.git"
146146+ else if repository.type == "GitLab" then
147147+ "${repository.server}/${repository.repo_path}.git"
148148+ else if repository.type == "Forgejo" then
149149+ "${repository.server}/${repository.owner}/${repository.repo}.git"
150150+ else
151151+ throw "Unrecognized repository type ${repository.type}";
152152+ urlToName =
153153+ url: rev:
154154+ let
155155+ matched = builtins.match "^.*/([^/]*)(\\.git)?$" url;
156156+157157+ short = builtins.substring 0 7 rev;
158158+159159+ appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
160160+ in
161161+ "${if matched == null then "source" else builtins.head matched}${appendShort}";
162162+ name = urlToName url revision;
163163+ in
164164+ fetchGit {
165165+ rev = revision;
166166+ narHash = hash;
167167+168168+ inherit name submodules url;
169169+ };
170170+171171+ mkPyPiSource =
172172+ { fetchurl, ... }:
173173+ {
174174+ url,
175175+ hash,
176176+ ...
177177+ }:
178178+ fetchurl {
179179+ inherit url;
180180+ sha256 = hash;
181181+ };
182182+183183+ mkChannelSource =
184184+ { fetchTarball, ... }:
185185+ {
186186+ url,
187187+ hash,
188188+ ...
189189+ }:
190190+ fetchTarball {
191191+ inherit url;
192192+ sha256 = hash;
193193+ };
194194+195195+ mkTarballSource =
196196+ { fetchTarball, ... }:
197197+ {
198198+ url,
199199+ locked_url ? url,
200200+ hash,
201201+ ...
202202+ }:
203203+ fetchTarball {
204204+ url = locked_url;
205205+ sha256 = hash;
206206+ };
207207+208208+ mkContainerSource =
209209+ pkgs:
210210+ {
211211+ image_name,
212212+ image_tag,
213213+ image_digest,
214214+ ...
215215+ }:
216216+ if pkgs == null then
217217+ builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers"
218218+ else
219219+ pkgs.dockerTools.pullImage {
220220+ imageName = image_name;
221221+ imageDigest = image_digest;
222222+ finalImageTag = image_tag;
223223+ };
224224+in
225225+mkFunctor (
226226+ {
227227+ input ? ./sources.json,
228228+ }:
229229+ let
230230+ data =
231231+ if builtins.isPath input then
232232+ # while `readFile` will throw an error anyways if the path doesn't exist,
233233+ # we still need to check beforehand because *our* error can be caught but not the one from the builtin
234234+ # *piegames sighs*
235235+ if builtins.pathExists input then
236236+ builtins.fromJSON (builtins.readFile input)
237237+ else
238238+ throw "Input path ${toString input} does not exist"
239239+ else if builtins.isAttrs input then
240240+ input
241241+ else
242242+ throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset";
243243+ version = data.version;
244244+ in
245245+ if version == 7 then
246246+ builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins
247247+ else
248248+ throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"
249249+)