···2233My repo serves as an educational example showing how [den](https://github.com/vic/den) and my related [libs](https://vic.github.io/dendrix/Dendritic-Ecosystem.html#vics-dendritic-libraries) structure a [Dendritic](https://vic.github.io/dendrix/Dendritic.html) NixOS setup with **named, composable aspects** instead of file imports. This is just one of many possible ways to organize a dendritic implementation. Feel free to explore, and share how you do things.
4455-65This specific setup is powered by **[den](https://github.com/vic/den) · [flake-aspects](https://github.com/vic/flake-aspects) · [denful](https://github.com/vic/denful) · [flake-file](https://github.com/vic/flake-file) · [import-tree](https://github.com/vic/import-tree) · [flake-parts](https://github.com/hercules-ci/flake-parts)**
7687--
···23222423```
2524modules/
2626-├── dendritic.nix # Bootstraps dendritic libs
2727-├── community/vix.nix # Creates vix.* namespace
2828-├── hosts.nix # Declares hosts, wires default profiles
2929-├── community/
3030-│ └── profile.nix # Defines host-profile and user-profile hooks
3131-├── hosts/
3232-│ └── nargun.nix # Composes aspects for nargun host
3333-└── vic/
3434- └── common-host-env.nix # Composes user environment aspects
3535-```
3636-3737-## Key Patterns
3838-3939-### 1. Custom Namespace ([`vix.nix`](modules/community/vix.nix))
4040-```nix
4141-den.aspects.vix = { }; # you can also use flake.aspects to expose all to flake.modules.
4242-flake.vix = config.den.aspects.vix._; # I just want to expose the vix aspect tree.
4343-_module.args.vix = config.den.aspects.vix._;
4444-```
4545-4646-Creates `vix.*` namespace. Everything under `vix` belongs to this config.
4747-4848-The `vix` namespace is written to directly, and read from module args, it is also shared as a flake output.
4949-Meaning other people can do `_module.args.vix = inputs.vix.vix`, and re-use modules from this repository's `community/` exactly as they are. Adopting a namespace for your aspects allows re-use for people using import-tree to load files from community repos.
5050-5151-5252-5353-### 2. Central Host Declaration ([`hosts.nix`](modules/hosts.nix))
5454-```nix
5555-den.hosts.x86_64-linux.nargun.users.vic = { };
5656-5757-den.default.host._.host.includes = [ vix.host-profile ];
5858-den.default.user._.user.includes = [ vix.user-profile ];
5959-```
6060-All hosts declared here. Default profiles automatically applied to every host/user.
6161-6262-### 3. Dynamic Profile Routing ([`profile.nix`](modules/community/profile.nix))
6363-```nix
6464-vix.host-profile = { host }: {
6565- # access host profile from vix, many modules can contribute to the same aspect.
6666- includes = [ vix.${host.name} ];
6767-};
6868-6969-vix.user-profile = { host, user }: {
7070- # users can enhance the host they are part of, by providing aspects to it.
7171- includes = [ (vix.${user.name}._.common-host-env { inherit host user; }) ];
7272-};
7373-```
7474-Profiles select aspects by host/user name. `vix.nargun` wired automatically for nargun host.
7575-7676-### 4. Aspect Composition with Variants ([`nargun.nix`](modules/hosts/nargun.nix))
7777-```nix
7878-vix.nargun._ = {
7979- base.includes = [ vix.dev-laptop ];
8080- hw.includes = [ vix.kvm-amd vix.niri-desktop ];
8181-};
8282-8383-vix.nargun.includes = [ vix.nargun._.base vix.nargun._.hw ];
8484-vix.nargun-vm.includes = [ vix.nargun._.base vix.nargun._.vm ];
8585-```
8686-Sub-aspects via `provides.X` become `_.X`. Hardware and VM share `base`.
8787-8888-### 5. User Environment Assembly ([`common-host-env.nix`](modules/vic/common-host-env.nix))
8989-```nix
9090-vix.vic._.common-host-env = { host, user }: {
9191- includes = map (f: f { inherit host user; }) [
9292- vix.vic._.admin
9393- vix.vic._.fish
9494- // ... more aspects
9595- ];
9696-};
9797-```
9898-User profile calls this with context. Each aspect receives `{ host, user }`.
9999-100100-### 6. Multi-Class Aspects ([`fish.nix`](modules/vic/fish.nix))
101101-```nix
102102-vix.vic._.fish = { user, ... }: {
103103- nixos.users.users.${user.userName}.shell = pkgs.fish;
104104- homeManager.programs.fish.enable = true;
105105-};
2525+├── dendritic.nix # Bootstraps dendritic libs
2626+├── namespace.nix # Creates `vix`, `vic`, `my` namespaces.
2727+├── my/ # Infra related aspects
2828+│ |── hosts.nix # Declares hosts, wires default profiles
2929+│ |── user.nix # Composes aspect used across all hosts.
3030+│ └── workstation.nix # Composes host setup.
3131+├── vic/ # User aspects and user settings.
3232+│ └── *.nix # many home-manager and os-config from vic.
3333+└── community/vix/ # Community shared aspects
3434+ └── *.nix # Exposed at flake.denful.vix
10635```
107107-Single aspect configures both system and home-manager.
108108-109109-## The Flow
110110-111111-1. **[`dendritic.nix`](modules/dendritic.nix)** loads dendritic libs
112112-2. **[`vix.nix`](modules/vix.nix)** creates namespace (`vix.*`)
113113-3. **[`hosts.nix`](modules/hosts.nix)** declares hosts and wires profiles:
114114- - `den.hosts.x86_64-linux.nargun.users.vic = { }`
115115- - Every host includes `vix.host-profile`
116116- - Every user includes `vix.user-profile`
117117-4. **[`profile.nix`](modules/community/profile.nix)** routes by name:
118118- - `vix.host-profile` → `vix.${host.name}` (e.g., `vix.nargun`)
119119- - `vix.user-profile` → `vix.${user.name}._.common-host-env`
120120-5. **[`nargun.nix`](modules/hosts/nargun.nix)** composes host aspects
121121-6. **[`common-host-env.nix`](modules/vic/common-host-env.nix)** composes user aspects
122122-123123-Result: Declare a host in one place, everything wires automatically via naming convention.
124124-125125-## Learning Path
126126-127127-Follow the flow above, then explore:
128128-- **[`fish.nix`](modules/vic/fish.nix)** - Simple parametric aspect
129129-- **[`unfree.nix`](modules/community/unfree.nix)** - Aspect factory pattern
130130-- **[`vm.nix`](modules/vm.nix)** - Package system as VM: `nix run github:vic/vix/den#vm`
131131-132132-## Why Dendritic?
133133-134134-135135-- Named aspects instead of manual imports.
136136-137137-- Functional Composition instead of Duplication.
138138-139139-- Parameters intead of Hardcoding.
140140-141141-- Sharing instead of Copy+Pasting.
···11+{
22+ lib,
33+ inputs,
44+ ...
55+}:
66+{
77+ jj-git-init.description = "init jj to follow git branch";
88+ jj-git-init.argumentNames = [ "branch" ];
99+ jj-git-init.body = ''
1010+ jj git init --colocate
1111+ jj bookmark track "$branch@origin"
1212+ jj config set --repo "revset-aliases.'trunk()'" "$branch@origin"
1313+ '';
1414+1515+ jj-desc.body = ''
1616+ jj describe --edit -m "$(echo -e "\n")$(jj status --color never | awk '{print "JJ: " $0}')$(echo -e "\n")$(jj show --git --color never | awk '{print "JJ: " $0}')"
1717+ '';
1818+1919+ mg.body = "spc u SPC gg -r \"$PWD\" RET";
2020+ spc.body = "SPC $argv -- -nw";
2121+ vspc.body = "SPC $argv -- -c";
2222+ fish_hybrid_key_bindings.description = "Vi-style bindings that inherit emacs-style bindings in all modes";
2323+ fish_hybrid_key_bindings.body = ''
2424+ for mode in default insert visual
2525+ fish_default_key_bindings -M $mode
2626+ end
2727+ fish_vi_key_bindings --no-erase
2828+ '';
2929+ vix-activate.description = "Activate a new vix system generation";
3030+ vix-activate.body = "nix run /hk/vix";
3131+ vix-shell.description = "Run nix shell with vix's nixpkgs";
3232+ vix-shell.body = "nix shell --inputs-from $HOME/.nix-out/nixpkgs";
3333+ vix-nixpkg-search.description = "Nix search on vix's nixpkgs input";
3434+ vix-nixpkg-search.body = "nix search --inputs-from $HOME/.nix-out/vix nixpkgs $argv";
3535+ rg-vix-inputs.description = "Search on vix flake inputs";
3636+ rg-vix-inputs.body =
3737+ let
3838+ maybeFlakePaths = f: if builtins.hasAttr "inputs" f then flakePaths f else [ ];
3939+ flakePaths =
4040+ flake: [ flake.outPath ] ++ lib.flatten (lib.mapAttrsToList (_: maybeFlakePaths) flake.inputs);
4141+ paths = builtins.concatStringsSep " " (flakePaths inputs.self);
4242+ in
4343+ "rg $argv ${paths}";
4444+ rg-vix.description = "Search on current vix";
4545+ rg-vix.body = "rg $argv $HOME/.nix-out/vix";
4646+ rg-nixpkgs.description = "Search on current nixpkgs";
4747+ rg-nixpkgs.body = "rg $argv $HOME/.nix-out/nixpkgs";
4848+ rg-home-manager.description = "Search on current home-manager";
4949+ rg-home-manager.body = "rg $argv $HOME/.nix-out/home-manager";
5050+ rg-nix-darwin.description = "Search on current nix-darwin";
5151+ rg-nix-darwin.body = "rg $argv $HOME/.nix-out/nix-darwin";
5252+ nixos-opt.description = "Open a browser on search.nixos.org for options";
5353+ nixos-opt.body = ''open "https://search.nixos.org/options?sort=relevance&query=$argv"'';
5454+ nixos-pkg.description = "Open a browser on search.nixos.org for packages";
5555+ nixos-pkg.body = ''open "https://search.nixos.org/packages?sort=relevance&query=$argv"'';
5656+ repology-nixpkgs.description = "Open a browser on search for nixpkgs on repology.org";
5757+ repology-nixpkgs.body = ''open "https://repology.org/projects/?inrepo=nix_unstable&search=$argv"'';
5858+}
+60
modules/vic/_fish/tv.fish
···11+bind \t __tv_complete
22+33+function __tv_complete -d 'fish completion widget with tv'
44+ # modified from https://github.com/junegunn/fzf/wiki/Examples-(fish)#completion
55+ # As of 2.6, fish's "complete" function does not understand
66+ # subcommands. Instead, we use the same hack as __fish_complete_subcommand and
77+ # extract the subcommand manually.
88+ set -l cmd (commandline -co) (commandline -ct)
99+1010+ switch $cmd[1]
1111+ case env sudo
1212+ for i in (seq 2 (count $cmd))
1313+ switch $cmd[$i]
1414+ case '-*'
1515+ case '*=*'
1616+ case '*'
1717+ set cmd $cmd[$i..-1]
1818+ break
1919+ end
2020+ end
2121+ end
2222+2323+ set -l cmd_lastw $cmd[-1]
2424+ set cmd (string join -- ' ' $cmd)
2525+2626+ set -l complist (complete -C$cmd)
2727+ set -l result
2828+2929+ # do nothing if there is nothing to select from
3030+ test -z "$complist"; and return
3131+3232+ set -l compwc (echo $complist | wc -w)
3333+ if test $compwc -eq 1
3434+ # if there is only one option dont open fzf
3535+ set result "$complist"
3636+ else
3737+ set result (string join -- \n $complist | column -t -l 2 -o \t | tv --select-1 --no-status-bar --keybindings='tab="confirm_selection"' --inline --input-header "$cmd" | string split -m 2 -f 1 \t | string trim --right)
3838+ end
3939+4040+ set -l prefix (string sub -s 1 -l 1 -- (commandline -t))
4141+ for i in (seq (count $result))
4242+ set -l r $result[$i]
4343+ switch $prefix
4444+ case "'"
4545+ commandline -t -- (string escape -- $r)
4646+ case '"'
4747+ if string match '*"*' -- $r >/dev/null
4848+ commandline -t -- (string escape -- $r)
4949+ else
5050+ commandline -t -- '"'$r'"'
5151+ end
5252+ case '~'
5353+ commandline -t -- (string sub -s 2 (string escape -n -- $r))
5454+ case '*'
5555+ commandline -t -- $r
5656+ end
5757+ commandline -i ' '
5858+ end
5959+ commandline -f repaint
6060+end
···11+# Do not modify this file! It was generated by ‘nixos-generate-config’
22+# and may be overwritten by future invocations. Please make changes
33+# to /etc/nixos/configuration.nix instead.
44+{
55+66+ flake.modules.nixos.mordor = {
77+ boot.initrd.availableKernelModules = [
88+ "xhci_pci"
99+ "ahci"
1010+ "usbhid"
1111+ "usb_storage"
1212+ "sd_mod"
1313+ ];
1414+ };
1515+}
+24
nix/hosts/nienna/configuration.nix
···11+# Edit this configuration file to define what should be installed on
22+# your system. Help is available in the configuration.nix(5) man page
33+# and in the NixOS manual (accessible by running ‘nixos-help’).
44+55+{
66+ inputs,
77+ ...
88+}:
99+let
1010+ flake.modules.nixos.nienna.imports = with inputs.self.modules.nixos; [
1111+ vic
1212+ xfce-desktop
1313+ macos-keys
1414+ kvm-intel
1515+ wl-broadcom
1616+ nienna-unfree
1717+ ];
1818+ nienna-unfree = inputs.self.lib.unfree-module [
1919+ "broadcom-sta"
2020+ ];
2121+in
2222+{
2323+ inherit flake;
2424+}
+24
nix/hosts/nienna/filesystems.nix
···11+# Do not modify this file! It was generated by ‘nixos-generate-config’
22+# and may be overwritten by future invocations. Please make changes
33+# to /etc/nixos/configuration.nix instead.
44+{
55+ flake.modules.nixos.nienna = {
66+77+ fileSystems."/" = {
88+ device = "/dev/disk/by-uuid/389d7756-a765-4be8-81eb-6712e893e705";
99+ fsType = "ext4";
1010+ };
1111+1212+ fileSystems."/boot" = {
1313+ device = "/dev/disk/by-uuid/67E3-17ED";
1414+ fsType = "vfat";
1515+ options = [
1616+ "fmask=0077"
1717+ "dmask=0077"
1818+ ];
1919+ };
2020+2121+ swapDevices = [ ];
2222+2323+ };
2424+}
+19
nix/hosts/nienna/hardware-configuration.nix
···11+# Do not modify this file! It was generated by ‘nixos-generate-config’
22+# and may be overwritten by future invocations. Please make changes
33+# to /etc/nixos/configuration.nix instead.
44+{
55+66+ flake.modules.nixos.nienna = {
77+ boot.initrd.availableKernelModules = [
88+ "uhci_hcd"
99+ "ehci_pci"
1010+ "ahci"
1111+ "firewire_ohci"
1212+ "usbhid"
1313+ "usb_storage"
1414+ "sd_mod"
1515+ "sdhci_pci"
1616+ ];
1717+ };
1818+1919+}
+15
nix/hosts/smaug/configuration.nix
···11+# Edit this configuration file to define what should be installed on
22+# your system. Help is available in the configuration.nix(5) man page
33+# and in the NixOS manual (accessible by running ‘nixos-help’).
44+{ inputs, ... }:
55+{
66+ flake.modules.nixos.smaug.imports = with inputs.self.modules.nixos; [
77+ vic
88+ xfce-desktop
99+ macos-keys
1010+ kvm-intel
1111+ wl-broadcom
1212+ nvidia
1313+ all-firmware
1414+ ];
1515+}
+32
nix/hosts/smaug/filesystems.nix
···11+# Do not modify this file! It was generated by ‘nixos-generate-config’
22+# and may be overwritten by future invocations. Please make changes
33+# to /etc/nixos/configuration.nix instead.
44+{
55+66+ flake.modules.nixos.smaug = {
77+88+ fileSystems."/" = {
99+ device = "/dev/disk/by-label/nixos";
1010+ fsType = "ext4";
1111+ };
1212+1313+ fileSystems."/boot" = {
1414+ device = "/dev/disk/by-label/boot";
1515+ fsType = "vfat";
1616+ options = [
1717+ "fmask=0077"
1818+ "dmask=0077"
1919+ ];
2020+ };
2121+2222+ fileSystems."/home" = {
2323+ device = "/dev/disk/by-label/home";
2424+ fsType = "ext4";
2525+ };
2626+2727+ swapDevices = [
2828+ { device = "/dev/disk/by-label/swap"; }
2929+ ];
3030+3131+ };
3232+}