nix run .#build # Build current host config
nix run .#build -- <hostname> # Build specific host (janet, tahani)
nix run .#apply # Build and apply locally (darwin-rebuild/nixos-rebuild switch)
nix flake check # Validate flakeDo not run build or apply unless instructed to.
alejandra . # Format all Nix files- Tool: Alejandra
- Config: default Alejandra formatting (spaces)
- Command: Run
alejandra .before committing
- Modules:
modules/- All configuration (flake-parts modules, auto-imported by import-tree)hosts/- Per-host composition modulesprofiles/- Shared host and user profile bundles_lib/- Utility functions (underscore = ignored by import-tree)_darwin/- Darwin-specific sub-modules_neovim/- Neovim plugin configshosts/_parts/- Host-specific leaf files (disk-config, hardware, service fragments, etc.)
- Apps:
apps/- Per-system app scripts - Secrets:
secrets/- SOPS-encrypted secrets (.sops.yamlfor config)
Framework: den (vic/den) — every .nix file in modules/ is a flake-parts module
Pattern: Feature/aspect-centric, not host-centric
Aspects: den.aspects.<name>.<class> where class is:
nixos- NixOS-only configurationdarwin- macOS-only configurationhomeManager- Home Manager configurationos- Applies to both NixOS and darwin
Hosts: den.hosts.<system>.<name> declared in modules/inventory.nix
Profiles: shared bundles live under modules/profiles/{host,user} and are exposed as den.aspects.host-* and den.aspects.user-*
Defaults: den.default.* defined in modules/defaults.nix
Inputs: foundational inputs live in modules/dendritic.nix; feature-specific flake-file.inputs live with their owning feature module
Imports: Auto-imported by import-tree; underscore-prefixed dirs (_lib/, _darwin/, etc.) are excluded from auto-import
Apply: use nix run .#apply for local application; darwin host janet is local-only
Function Arguments:
{inputs, pkgs, lib, ...}:Use ... to capture remaining args. Let Alejandra control the exact layout.
Attribute Sets:
den.aspects.myfeature.os = {
enable = true;
config = "value";
};One attribute per line with trailing semicolons.
Lists with Packages:
with pkgs;
[
age
alejandra
ast-grep
]Use with pkgs; for package lists, one item per line.
Aspect Definition:
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.den.aspects.myfeature;
in {
options.den.aspects.myfeature = {
enable = mkEnableOption "Feature description";
};
config = mkIf cfg.enable {
# configuration
};
}- Use
with lib;for brevity with NixOS lib functions - Define
cfgfor config options - Use
mkIf,mkForce,mkDefaultappropriately
Conditional Platform-Specific Code:
++ lib.optionals stdenv.isDarwin [
_1password-gui
dockutil
]
++ lib.optionals stdenv.isLinux [
lm_sensors
]- Aspect names:
den.aspects.<name>.<class>for feature configuration - Hostnames: Lowercase, descriptive (e.g.,
tahani,janet) - Module files: Descriptive, lowercase with hyphens (e.g.,
neovim-config.nix)
- Use SOPS for secrets (see
.sops.yaml) - Never commit unencrypted secrets
- Secret definitions live with the feature that consumes them; host aspects include the feature on the required OS/user scopes
- Shared SOPS defaults (module imports, key paths) in
modules/secrets.nix
Use den.aspects.<name>.includes to compose aspects:
den.aspects.myfeature.includes = [
"other-aspect"
"another-aspect"
];- No
specialArgs— den batteries handle input passing - No hostname string comparisons in shared aspects
- Host-specific config goes in
den.aspects.<hostname>.* - Shared config uses
osclass (applies to both NixOS and darwin) - Non-module files go in
_-prefixed subdirs