A dendritic Nix flake for managing NixOS, nix-darwin, WSL, Home Manager, packages, secrets, and deployment behavior from a centralized declarative source of truth.
It follows the dendritic Nix pattern through the Den framework, and is meant to be explored through the flake structure rather than through a hand-maintained inventory. The evaluated flake remains authoritative for the current set of hosts, aspects, packages, checks, and deployment outputs.
The repository is organized around Den/dendritic composition rather than a flat collection of host files. Host and user metadata define the topology, while reusable aspects attach system, service, hardware, monitoring, deployment, and Home Manager behavior where applicable.
flake.nix is generated by flake-file and kept intentionally small. The configuration model starts in modules/dendritic.nix, which wires flake-file, Den, and the whitestrake namespace together. The topology is then split across focused modules: modules/hosts.nix declares hosts and users; modules/defaults.nix defines global Den defaults.
Aspects describe capabilities, roles, and reusable behavior. Host-specific directories primarily compose those capabilities and hold local details such as hardware profiles, disk layout, or machine-specific overrides. This keeps most changes reviewable as shared configuration rather than isolated host mutations.
The whitestrake Den namespace exports reusable personal aspects through flake.denful.whitestrake. Downstream Den flakes can import that namespace and opt into aspects such as whitestrake.user and whitestrake.dev-tools independently.
The following paths are the main landmarks for navigating the repository:
flake.nix: The generated flake root. Do not edit it directly; updateflake-filedeclarations in the module tree and regenerate it withnix run .#write-flake.modules/dendritic.nix: The main dendritic wiring module. It imports flake-file, Den, and the repository namespace, and declares the foundational generated-flake metadata and inputs.modules/hosts.nix: The inventory of declared machines and their Den user membership.modules/defaults.nix: Global Den defaults for NixOS, nix-darwin, and Home Manager classes.modules/secrets/: SOPS integration and encrypted secret material governed by.sops.yaml.modules/: The primary module tree for flake, Den, host, deployment, package, and reusable configuration logic.modules/aspects/: The main reusable composition layer. Aspects describe thematic or capability-based configuration such as base system behavior, services, monitoring, deployment support, hardware roles, or host classes.modules/aspects/hosts/: Host-specific composition. Each host directory composes reusable aspects and carries local machine details.- Host-private files: Files such as
_hardware.nixor_disko.nix, where present, are local to a host and imported explicitly. They are not reusable aspects. packages/: Custom packages, package definitions, and overrides that are not provided directly by Nixpkgs in the required form..github/workflows/: GitHub Actions workflows for validation, builds, deployment planning, dependency updates, and flake maintenance.
The repository is maintained with a pragmatic branching model based on risk and review value.
- Trivial changes: Small administrative or low-risk changes may be committed directly to
masterwhere appropriate. - Lightly experimental changes: A feature branch may be used for local or CI validation before merging back to
master. - Large or risky changes: A feature branch and pull request should be used to document, review, iterate, and preserve context before merge.
- Canary deployments: Significant changes can be tested from a branch by manually dispatching deployment workflows against a designated canary host before broader rollout.
The goal is to match the amount of review and deployment caution to the operational risk of the change.
The repository uses formatting and diagnostic tools to keep routine issues out of review. All formatting and Nix diagnostics are unified under a single entrypoint:
- Unified Check & Format: treefmt serves as the
nix fmtentrypoint for the entire project. - Formatting: It automatically routes files to alejandra (Nix), yamlfmt (YAML), and mdformat (Markdown).
- Diagnostics: It leverages
treefmt's "check-only" capability to run both nil diagnostics across all.nixfiles (failing on syntax errors or unused bindings) and actionlint to validate GitHub workflow syntax.
These tools support consistency and fast feedback. They are not a substitute for evaluating the flake, checking generated outputs, or considering deployment safety.
CI is structured to separate evaluation, validation, building, deployment planning, and host-side safety checks.
- Evaluation and structural validation: Broad flake and schema checks catch broken composition early without forcing unnecessary full-system realization on every path.
- Targeted builds: Host, package, and check builds are kept as focused as practical. Cachix is used to avoid repeated work and to make successful artifacts available to later jobs and hosts.
- Deployment planning: Deployment jobs derive their target matrix dynamically from changed host outputs and deploy-agent availability.
- Primary deployment path: Cachix Deploy is the normal automated pull-based deployment mechanism.
- Fallback deployment path: Direct
nixos-rebuild --target-host --build-host --sudoremains available where the Cachix Deploy path is not appropriate. - Dependency maintenance: Automated workflows update flake inputs, custom packages, GitHub Actions, and other non-Nix dependencies.
A successful CI build proves that the relevant configuration evaluates and builds. It does not, by itself, prove that an activated host remains reachable or healthy after deployment. That boundary is handled separately by deployment gating and host-side checks.
Secrets are encrypted with SOPS and integrated through sops-nix. Access policy is defined by age recipients in .sops.yaml.
Shared encrypted payloads live under modules/secrets/secrets.yaml alongside the SOPS module that wires them into the repository's NixOS and nix-darwin defaults. Host access is controlled by adding the appropriate recipient keys to the SOPS policy and updating encrypted files with the expected key set.
When introducing a new host, the bootstrap flow pre-creates the host SSH key locally. The corresponding age recipient is derived from that host key and added to .sops.yaml before the machine goes live. This allows sops-nix on the target host to derive the expected identity from the system SSH host key and decrypt the secrets required for its role.
The repository distinguishes between build-time validation, deploy-time gating, and host-side post-activation health checks.
Build-time validation catches evaluation errors, failed builds, broken checks, and invalid generated outputs. Deploy-time gating decides which hosts should receive a deployment and whether the relevant deploy agent is available. Host-side health checks validate that the activated system is actually usable after switching generations.
Cachix Deploy hosts use a post-activation health and rollback script generated from the inventory modules and host deployment aspects. After activation, the host validates critical local services and access paths. If required checks fail, the deployment is treated as failed and the host can roll back to the previous generation.
Health checks should be host-aware. Different machines expose different critical services, so checks should be attached through reusable capabilities, aspects, or host metadata rather than hardcoded hostname exceptions. Remote access paths such as SSH and Tailscale should be treated as lockout-critical where applicable.
New hosts are introduced from a local feature branch and provisioned with nixos-anywhere.
The bootstrap process pre-creates the host SSH key, derives the corresponding SOPS age recipient, updates the secrets policy, provisions the target machine, and adds the resulting host composition and hardware details to the flake.
By the time the host branch is merged, the machine should already exist, have its expected network access paths, and be able to decrypt the secrets required for its role. Host inclusion should be treated as an operational change, not only a flake edit.
The repository is easiest to understand by following the composition model rather than by looking for a single exhaustive configuration file.
flake.nix shows the generated top-level inputs and dendritic output shape. From there, read modules/dendritic.nix, then modules/hosts.nix and modules/defaults.nix to understand how the Den topology is assembled.
Host configuration flows from modules/aspects/hosts/<hostname> into the aspects it includes. Reusable aspects generally explain shared behavior better than host-specific files, while host-private files such as hardware profiles and disk layouts provide the local details needed by an individual machine.
The CI workflows are best read in terms of which flake outputs they evaluate, build, cache, or deploy. Deployment and health-check modules are worth treating as operational safety boundaries rather than ordinary service configuration.