diff --git a/README.md b/README.md index 0545f94..e601b9f 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,17 @@ den -- Dendritic: same concern, different classes and context-aware. +- Dendritic: each module configures **same** concern over **different** Nix classes. -- Small, [DRY](modules/aspects/provides/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) modules. +- Create [DRY](modules/aspects/provides/unfree.nix) & [`class`-generic](modules/aspects/provides/primary-user.nix) modules. - [Parametric](modules/aspects/provides/define-user.nix) over `host`/`home`/`user`. -- [Share](templates/examples/modules/_profile/namespace.nix) aspects across systems & repos. +- [Share](templates/default/modules/namespace.nix) aspects across systems & repos. -- Bidirectional [dependencies](modules/aspects/dependencies.nix): user/host contributions. +- Context-aware [dependencies](modules/aspects/dependencies.nix): user/host contributions. + +- [Routable](templates/default/modules/aspects/eg/routes.nix) configurations. - Custom factories for any Nix `class`. @@ -38,7 +40,11 @@ - [Batteries](modules/aspects/provides/): Opt-in, replaceable aspects. -- [Well-tested](templates/examples/modules/_example/ci) with [examples](templates/examples). +- Opt-in [``](https://vic.github.io/den/angle-brackets.html) aspect resolution. + +- Templates [tested](templates/default/modules/tests.nix) along [examples](templates/examples/modules/_example/ci). + +- Concepts [documented](https://vic.github.io/den). Need more batteries? See [vic/denful](https://github.com/vic/denful). @@ -52,15 +58,21 @@ See schema in [`_types.nix`](modules/_types.nix). ```nix # modules/hosts.nix -# OS & standalone homes share 'vic' aspect. -# $ nixos-rebuild switch --flake .#my-laptop -# $ home-manager switch --flake .#vic { - den.hosts.x86-64-linux.laptop.users.vic = {}; + # same home-manager vic configuration + # over laptop, macbook and standalone-hm + den.hosts.x86_64-linux.lap.users.vic = {}; + den.hosts.aarch64-darwin.mac.users.vic = {}; den.homes.aarch64-darwin.vic = {}; } ``` +```console +$ nixos-rebuild switch --flake .#lap +$ darwin-rebuild switch --flake .#mac +$ home-manager switch --flake .#vic +``` + 🧩 [Aspect-oriented](https://github.com/vic/flake-aspects) incremental features. ([example](templates/default/modules/den.nix)) Any module can contribute configurations to aspects. @@ -87,7 +99,7 @@ Any module can contribute configurations to aspects. # User contribs to host nixos.users.users = { vic.description = "oeiuwq"; - } + }; includes = [ den.aspects.tiling-wm den._.primary-user diff --git a/nix/den-brackets.nix b/nix/den-brackets.nix index 2fd6b21..1ff95e7 100644 --- a/nix/den-brackets.nix +++ b/nix/den-brackets.nix @@ -1,9 +1,5 @@ # __findFile implementation to resolve deep aspects. # inspired by https://fzakaria.com/2025/08/10/angle-brackets-in-a-nix-flake-world -# -# For user facing documentation, see: -# See templates/default/_profile/den-brackets.nix -# See templates/default/_profile/namespace.nix { lib, config, @@ -21,14 +17,23 @@ let notFound = "Aspect not found: ${lib.concatStringsSep "." path}"; headIsDen = head == "den"; - readFromDen = lib.getAttrFromPath tail config.den; + readFromDen = lib.getAttrFromPath ([ "den" ] ++ tail) config; headIsAspect = builtins.hasAttr head config.den.aspects; - readFromAspects = lib.getAttrFromPath path config.den.aspects; + aspectsPath = [ + "den" + "aspects" + ] ++ path; + readFromAspects = lib.getAttrFromPath aspectsPath config; headIsDenful = lib.hasAttrByPath [ "ful" head ] config.den; denfulTail = if lib.head tail == "provides" then lib.tail tail else tail; - readFromDenful = lib.getAttrFromPath ([ head ] ++ denfulTail) config.den.ful; + denfulPath = [ + "den" + "ful" + head + ] ++ denfulTail; + readFromDenful = lib.getAttrFromPath denfulPath config; found = if headIsDen then diff --git a/templates/default/modules/aspects/alice.nix b/templates/default/modules/aspects/alice.nix index 289baed..9274926 100644 --- a/templates/default/modules/aspects/alice.nix +++ b/templates/default/modules/aspects/alice.nix @@ -30,5 +30,12 @@ { home.packages = [ pkgs.htop ]; }; + + # .provides., via eg/routes.nix + provides.igloo = + { host, ... }: + { + nixos.programs.nh.enable = host.name == "igloo"; + }; }; } diff --git a/templates/default/modules/aspects/defaults.nix b/templates/default/modules/aspects/defaults.nix index 845f906..00df1c5 100644 --- a/templates/default/modules/aspects/defaults.nix +++ b/templates/default/modules/aspects/defaults.nix @@ -16,6 +16,9 @@ # These are functions that produce configs den.default.includes = [ + # ${user}.provides.${host} and ${host}.provides.${user} + + # Enable home-manager on all hosts. diff --git a/templates/default/modules/aspects/eg/routes.nix b/templates/default/modules/aspects/eg/routes.nix new file mode 100644 index 0000000..ec6f4df --- /dev/null +++ b/templates/default/modules/aspects/eg/routes.nix @@ -0,0 +1,53 @@ +# This example implements an aspect "routing" pattern. +# +# Unlike `den.default` which is `parametric.atLeast` we use `parametric.exactly` here +# to be more strict and prevent multiple values inclusion. +# +# Be sure to read: https://vic.github.io/den/dependencies.html +# See usage at: defaults.nix, alice.nix, igloo.nix +# +{ den, eg, ... }: +{ + # Usage: `den.default.includes [ eg.routes ]` + eg.routes = + let + inherit (den.lib) parametric; + + os-from-user = + { + user, + host, + # deadnix: skip + OS, + # deadnix: skip + fromUser, + }: + parametric { inherit user host; } (mutual user host); + + hm-from-host = + { + user, + host, + # deadnix: skip + HM, + # deadnix: skip + fromHost, + }: + parametric { inherit user host; } (mutual host user); + + mutual = from: to: { + includes = [ + # eg, `._.` and `._.` + (den.aspects.${from.aspect}._.${to.aspect} or { }) + ]; + }; + + in + { + __functor = parametric.exactly; + includes = [ + os-from-user + hm-from-host + ]; + }; +} diff --git a/templates/default/modules/aspects/igloo.nix b/templates/default/modules/aspects/igloo.nix index b5c1e31..eab55eb 100644 --- a/templates/default/modules/aspects/igloo.nix +++ b/templates/default/modules/aspects/igloo.nix @@ -16,5 +16,12 @@ eg.vm-bootable eg.xfce-desktop ]; + + # .provides., via eg/routes.nix + provides.alice = + { user, ... }: + { + homeManager.programs.helix.enable = user.name == "alice"; + }; }; } diff --git a/templates/default/modules/tests.nix b/templates/default/modules/tests.nix new file mode 100644 index 0000000..3a841b2 --- /dev/null +++ b/templates/default/modules/tests.nix @@ -0,0 +1,25 @@ +# Some CI checks to ensure this template always works. +# Feel free to adapt or remove when this repo is yours. +{ inputs, ... }: +{ + perSystem = + { pkgs, self', ... }: + let + checkCond = name: cond: pkgs.runCommandLocal name { } (if cond then "touch $out" else ""); + apple = inputs.self.darwinConfigurations.apple.config; + igloo = inputs.self.nixosConfigurations.igloo.config; + alice-at-igloo = igloo.home-manager.users.alice; + vmBuilds = !pkgs.stdenvNoCC.isLinux || builtins.pathExists (self'.packages.vm + "/bin/vm"); + iglooBuilds = !pkgs.stdenvNoCC.isLinux || builtins.pathExists (igloo.system.build.toplevel); + appleBuilds = !pkgs.stdenvNoCC.isDarwin || builtins.pathExists (apple.system.build.toplevel); + in + { + checks."igloo builds" = checkCond "igloo-builds" iglooBuilds; + checks."apple builds" = checkCond "apple-builds" appleBuilds; + checks."vm builds" = checkCond "vm-builds" vmBuilds; + + checks."alice enabled igloo nh" = checkCond "alice.provides.igloo" igloo.programs.nh.enable; + checks."igloo enabled alice helix" = + checkCond "igloo.provides.alice" alice-at-igloo.programs.helix.enable; + }; +} diff --git a/templates/examples/modules/_example/README.md b/templates/examples/modules/_example/README.md index 39d3caf..687bbc5 100644 --- a/templates/examples/modules/_example/README.md +++ b/templates/examples/modules/_example/README.md @@ -1,15 +1,5 @@ User TODO: REMOVE this directory (or disable its import from den.nix) -It is used to implement tests for all den feature so we can validate at CI. +It is used to implement tests for all den features so we can validate at CI. -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. - -For a more "real-world" layout, see the `_profile` directory -which is somewhat inspired on the -dendritic implementation at [vic/vix](https://github.com/vic/vix/tree/8c8c7b8). - -However, feel free to also not use any predefined layout, explore by yourself -and find out how things work for you. Be sure to share your insights with -the [community](https://github.com/vic/den/discussions) +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. diff --git a/templates/examples/modules/_example/ci/import-tree.nix b/templates/examples/modules/_example/ci/import-tree.nix index 8a7b02b..a9a8da2 100644 --- a/templates/examples/modules/_example/ci/import-tree.nix +++ b/templates/examples/modules/_example/ci/import-tree.nix @@ -1,7 +1,7 @@ # configures class-automatic module auto imports for hosts/users/homes. # See documentation at modules/aspects/provides/import-tree.nix { - # deadnix: skip # see _profile/den-brackets.nix + # deadnix: skip __findFile ? __findFile, ... }: diff --git a/templates/examples/modules/_example/ci/namespace.nix b/templates/examples/modules/_example/ci/namespace.nix new file mode 100644 index 0000000..766a95f --- /dev/null +++ b/templates/examples/modules/_example/ci/namespace.nix @@ -0,0 +1,5 @@ +{ inputs, den, ... }: +{ + imports = [ (inputs.den.namespace "eg" false) ]; + _module.args.__findFile = den.lib.__findFile; +} diff --git a/templates/examples/modules/_profile/README.md b/templates/examples/modules/_profile/README.md deleted file mode 100644 index 3bddbd8..0000000 --- a/templates/examples/modules/_profile/README.md +++ /dev/null @@ -1,12 +0,0 @@ -User TODO: REMOVE this directory (or disable its import from den.nix). -Move any module you find useful from here into your /modules directory. - -This directory contains a bare-bones layout for using den. -It is inspired on the patterns shown in the -dendritic implementation at [vic/vix](https://github.com/vic/vix/tree/den). - -Use it as reference of how to organize things. - -Feel free to adapt the layout, explore by yourself -and find out how things work for you. Be sure to share your insights with -the [community](https://github.com/vic/den/discussions) diff --git a/templates/examples/modules/_profile/den-brackets.nix b/templates/examples/modules/_profile/den-brackets.nix deleted file mode 100644 index 3af0726..0000000 --- a/templates/examples/modules/_profile/den-brackets.nix +++ /dev/null @@ -1,36 +0,0 @@ -# This enables den's angle brackets opt-in feature. -# Remove this file to opt-out. -# -# When den.lib.__findFile is in scope, you can do: -# -# and it will resolve to: -# den.aspects.pro.provides.foo.provides.bar -# -# resolves to: -# den.aspects.pro.provides.foo.includes -# -# resolves to: -# den.provides.import-tree.provides.home -# -# resolves to den.default -# -# When the vix remote namespace is enabled -# resolves to: den.ful.vix.provides.foo -# -# Usage: -# -# Bring `__findFile` into scope from module args: -# -# { __findFile, ... }: -# den.default.includes = [ ]; -# } -# -# IF you are using nixf-diagnose, it will complain -# about __findFile not being used, trick it with: -# -# { __findFile ? __findFile, ... } -# -{ den, ... }: -{ - _module.args.__findFile = den.lib.__findFile; -} diff --git a/templates/examples/modules/_profile/hosts.nix b/templates/examples/modules/_profile/hosts.nix deleted file mode 100644 index 2dc1f21..0000000 --- a/templates/examples/modules/_profile/hosts.nix +++ /dev/null @@ -1,3 +0,0 @@ -{ - den.hosts.x86_64-linux.bones.users.fido = { }; -} diff --git a/templates/examples/modules/_profile/hosts/bones/common-user-env.nix b/templates/examples/modules/_profile/hosts/bones/common-user-env.nix deleted file mode 100644 index 07b5c8a..0000000 --- a/templates/examples/modules/_profile/hosts/bones/common-user-env.nix +++ /dev/null @@ -1,19 +0,0 @@ -# An aspect that contributes to any user home on the bones host. -{ ... }: -let - # private aspects can be let-bindings - # more re-usable ones are better defined inside the `pro` namespace. - host-contrib-to-user = - { hostToUser, ... }: - if hostToUser.host.name == "bones" || hostToUser.user.name == "fido" then - { - homeManager.programs.vim.enable = true; - } - else - { }; -in -{ - den.default.includes = [ - host-contrib-to-user - ]; -} diff --git a/templates/examples/modules/_profile/namespace.nix b/templates/examples/modules/_profile/namespace.nix deleted file mode 100644 index 9d81577..0000000 --- a/templates/examples/modules/_profile/namespace.nix +++ /dev/null @@ -1,67 +0,0 @@ -# This module creates an aspect namespace. -# -# Just add the following import: -# -# # define local namespace. enable flake output. -# imports = [ (inputs.den.namespace "vix" true) ]; -# -# # you can use remote namespaces and they will merge -# imports = [ (inputs.den.namespace "vix" inputs.dendrix) ]; -# -# Internally, a namespace is just a `provides` branch: -# -# # den.ful is the social-convention for namespaces. -# den.ful. -# -# Having an aspect namespace is not required but helps a lot -# with organization and conventient access to your aspects. -# -# The following examples use the `vix` namespace, -# inspired by github:vic/vix own namespace pattern. -# -# By using an aspect namespace you can: -# -# - Directly write to aspects in your namespace. -# -# { -# vix.gaming.nixos = ...; -# -# # instead of: -# # den.ful.vix.gaming.nixos = ...; -# } -# -# - Directly read aspects from your namespace. -# -# # Access the namespace from module args -# { vix, ... }: -# { -# den.default.includes = [ vix.security ]; -# -# # instead of: -# # den.default.includes = [ den.ful.vix.security ]; -# } -# -# - Share and re-use aspects between Dendritic flakes -# -# # Aspects opt-in exposed as flake.denful. -# { imports = [( inputs.den.namespace "vix" true)] } -# -# # Many flakes can expose to the same namespace and we -# # can merge them, accessing aspects in a uniform way. -# { imports = [( inputs.den.namespace "vix" inputs.dendrix )] } -# -# - Use angle-brackets to access deeply nested trees -# -# # Be sure to read _profile/den-brackets.nix -# { __findFile, ... }: -# den.aspects.my-laptop.includes = [ ]; -# } -# -# -# You can of course choose to not have any of the above. -# USER TODO: Remove this file for not using a namespace. -# USER TODO: Replace `pro` and update other files using it. -{ inputs, ... }: -{ - imports = [ (inputs.den.namespace "pro" true) ]; -} diff --git a/templates/examples/modules/_profile/profiles.nix b/templates/examples/modules/_profile/profiles.nix deleted file mode 100644 index dfbf837..0000000 --- a/templates/examples/modules/_profile/profiles.nix +++ /dev/null @@ -1,19 +0,0 @@ -# Profiles are just aspects whose only job is to include other aspects -# based on the properties (context) of the host/user they are included in. -{ pro, den, ... }: -{ - - # install profiles as parametric aspects on all hosts/users - den.default.includes = [ - pro.profiles - ]; - - pro.profiles = { - __functor = den.lib.parametric true; - includes = [ - ({ host, ... }: pro.${host.system} or { }) - # add other routes according to context. - ]; - }; - -} diff --git a/templates/examples/modules/_profile/profiles/linux-utils-for-macos.nix b/templates/examples/modules/_profile/profiles/linux-utils-for-macos.nix deleted file mode 100644 index b8a8a90..0000000 --- a/templates/examples/modules/_profile/profiles/linux-utils-for-macos.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ - - # example custom profile per platform system, see profiles.nix - pro.aarch64-darwin.darwin = - { pkgs, ... }: - { - # provide a consistent environment with linux. - environment.systemPackages = [ - pkgs.coreutils - pkgs.util-linux - ]; - }; - -} diff --git a/templates/examples/modules/_profile/profiles/single-user-is-admin.nix b/templates/examples/modules/_profile/profiles/single-user-is-admin.nix deleted file mode 100644 index 8ee4c84..0000000 --- a/templates/examples/modules/_profile/profiles/single-user-is-admin.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ den, lib, ... }: -{ - - # When a host includes *ONLY* one user, make that user the admin. - pro.single-user-is-admin = - { userToHost, ... }@context: - let - inherit (userToHost) user host; - single = 1 == builtins.length (builtins.attrValues host.users); - exists = single && builtins.hasAttr user.name host.users; - admin = lib.optionals exists [ den._.primary-user ]; - in - { - __functor = den.lib.parametric context; - includes = [ den._.define-user ] ++ admin; - }; -} diff --git a/templates/examples/modules/_profile/users/fido/common-host-env.nix b/templates/examples/modules/_profile/users/fido/common-host-env.nix deleted file mode 100644 index 0246263..0000000 --- a/templates/examples/modules/_profile/users/fido/common-host-env.nix +++ /dev/null @@ -1,16 +0,0 @@ -# An aspect that contributes to any operating system where fido is a user. -# hooks itself into any host. -{ pro, ... }: -let - fido-at-host = - { userToHost, ... }: - if userToHost.user.name != "fido" then { } else pro.fido._.${userToHost.host.name}; -in -{ - den.default.includes = [ - fido-at-host - ]; - - # fido on bones host. - pro.fido._.bones.nixos = { }; -} diff --git a/templates/examples/modules/den.nix b/templates/examples/modules/den.nix index 48918c0..7f908dc 100644 --- a/templates/examples/modules/den.nix +++ b/templates/examples/modules/den.nix @@ -6,8 +6,5 @@ # The _example directory contains CI tests for all den features. # use it as reference of usage, but not of best practices. (inputs.import-tree ./_example) - - # The _profile directory contains a minimal profile-based layout. - (inputs.import-tree ./_profile) ]; }