A NixOS configuration using the Dendritic Pattern - an organic configuration growth pattern with automatic module discovery.
Based on the golden standard from mightyiam/infra.
Nix files (they're all flake-parts modules) are automatically imported. Nix files prefixed with an underscore are ignored. No literal path imports are used. This means files can be moved around and nested in directories freely.
Note
This pattern has been the inspiration of an auto-imports library, import-tree.
Modules register themselves under two mergeable aggregators:
flake.nixosModules: NixOS modules (freeform, nested namespaces allowed)flake.homeManagerModules: Home Manager modules (freeform; withbase,gui, and per-app underapps)
Composition now centers on the single System76 host, so imports reference the exact feature modules that machine needs:
{ config, lib, ... }:
{
configurations.nixos.system76.module = {
imports = lib.filter (module: module != null) [
(config.flake.nixosModules.base or null)
(config.flake.nixosModules."system76-support" or null)
(config.flake.nixosModules."hardware-lenovo-y27q-20" or null)
];
};
}Continue to use lib.hasAttrByPath and lib.getAttrFromPath when selecting optional modules to avoid ordering issues.
All packages and services now live under modules/system76/. Each file contributes directly to configurations.nixos.system76.module, so the host is assembled from explicit feature modules rather than abstract roles.
Highlights:
modules/system76/packages.nix– core packages and unfree allow-list for the System76 laptop.modules/system76/dev-languages.nix– imports language toolchains viaflake.nixosModules.apps.<name>for Python, Go, Rust, and Clojure.modules/system76/home-manager-gui.nix– wires the shared GUI Home Manager module and any extra app imports exposed by other modules.modules/system76/security-tools.nix,modules/system76/sudo.nix,modules/system76/zsh.nix, etc. – replace the old workstation bundle with host-scoped modules.
Because there is only one host (configurations.nixos.system76), you can follow the code in modules/system76/ to understand exactly how the system is configured without navigating role indirection.
Enter the development shell:
nix developUseful commands:
nix fmt– format filespre-commit run --all-files– run all hooks
The build.sh helper refuses to run if the git worktree is dirty (tracked changes, staged changes, or untracked files) to keep builds reproducible. Override with --allow-dirty or ALLOW_DIRTY=1 only when you know what you’re doing.
- Encrypt the payload – run
sops secrets/<name>.yaml(orsops -e -i …) so the file is stored as ciphertext. The canonical.sops.yamlin this repo already targets everything undersecrets/. - Declare the secret in Nix – add an entry under
sops.secrets."<namespace>/<name>"(system or Home Manager). PointsopsFileto the encrypted file, setkeywhen selecting a single YAML attribute, and write the decrypted material to a runtime path using%r. - Consume via the module API – reference
config.sops.secrets."<namespace>/<name>".path(orplaceholder) from services, wrappers, or templates. Never read secrets at evaluation time.
Example (Context7 MCP key for Codex):
sops.secrets."context7/api-key" = {
sopsFile = ./../../secrets/context7.yaml;
key = "context7_api_key";
path = "%r/context7/api-key";
mode = "0400";
};The Codex module wraps the decrypted path in a small script and only enables the MCP server when the secret exists, keeping evaluation pure while allowing runtime access.
The following files in this repository are generated and checked using the files flake-parts module:
.actrc.gitignore.sops.yamlREADME.md
Some explicit flake inputs exist solely for the purpose of deduplication.
They are the target of at least one <input>.inputs.<input>.follows.
But what if in the future all of those targeting follows are removed?
Ideally, Nix would detect that and warn.
Until that feature is available those inputs are prefixed with dedupe_
and placed in an additional separate inputs attribute literal
for easy identification.
This at the top level of the flake.nix file:
nixConfig.abort-on-warn = true;Note
It does not currently catch all warnings Nix can produce, but perhaps only evaluation warnings.